diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 2b41790103e..7dee695c2d5 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -736,7 +736,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." desc = "Laws have potentially been uploaded to or removed from this unit. Please be aware of any changes \ so as to remain in compliance with the most up-to-date laws." icon_state = ALERT_NEW_LAW - timeout = 300 + timeout = 30 SECONDS /atom/movable/screen/alert/hackingapc name = "Hacking APC" @@ -744,7 +744,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." complete, you will have exclusive control of it, and you will gain \ additional processing time to unlock more malfunction abilities." icon_state = ALERT_HACKING_APC - timeout = 600 + timeout = 60 SECONDS var/atom/target = null /atom/movable/screen/alert/hackingapc/Click() diff --git a/code/datums/ai_laws/ai_laws.dm b/code/datums/ai_laws/ai_laws.dm index 28414b799ca..cdd01992249 100644 --- a/code/datums/ai_laws/ai_laws.dm +++ b/code/datums/ai_laws/ai_laws.dm @@ -101,16 +101,41 @@ return null /datum/ai_laws + /// The name of the lawset var/name = "Unknown Laws" - var/zeroth = null - var/zeroth_borg = null - var/list/inherent = list() - var/list/supplied = list() - var/list/ion = list() - var/list/hacked = list() + /// The silicon linked to this lawset var/mob/living/silicon/owner + /// The ID of this lawset, pretty much only used to tell if we're default or not var/id = DEFAULT_AI_LAWID + /// If TRUE, the zeroth law of this AI is protected and cannot be removed by players under normal circumstances. + var/protected_zeroth = FALSE + + /// Zeroth law + /// A lawset can only have 1 zeroth law, it's the top dog. + /// Nothing removes it unless it's admin forced + var/zeroth = null + /// Zeroth borg law + /// It's just a zeroth law but specially themed for cyborgs + /// ("follow your master" vs "accomplish your objectives") + var/zeroth_borg = null + /// Core laws + /// Inherent laws are the "core" laws of the AI + /// Reseting the AI will not remove these, these are intrinsit to whatever lawset they are running. + var/list/inherent = list() + /// Supplied laws + /// Supplied laws are supplied in addition to the inherent laws - after the fact + /// These laws will go away when an AI is reset + var/list/supplied = list() + /// Ion laws + /// Special randomized (usually) laws which are above all over laws + /// These laws will go away when an AI is reset + var/list/ion = list() + /// Hacked laws + /// Syndicate uploaded laws which are above all other laws + /// These laws will go away when an AI is reset + var/list/hacked = list() + /datum/ai_laws/Destroy(force = FALSE, ...) if(!QDELETED(owner)) //Stopgap to help with laws randomly being lost. This stack_trace will hopefully help find the real issues. if(force) //Unless we're forced... @@ -153,7 +178,14 @@ default_laws = new default_laws() inherent = default_laws.inherent -/datum/ai_laws/proc/get_law_amount(groups) +/** + * Gets the number of how many laws this AI has + * + * * groups - What groups to count laws from? By default counts all groups + * + * Returns a number, the number of laws we have + */ +/datum/ai_laws/proc/get_law_amount(list/groups = list(LAW_ZEROTH, LAW_ION, LAW_HACKED, LAW_INHERENT, LAW_SUPPLIED)) var/law_amount = 0 if(zeroth && (LAW_ZEROTH in groups)) law_amount++ @@ -170,37 +202,141 @@ law_amount++ return law_amount -/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null) +/** + * Sets this lawset's zeroth law to the passed law + * + * Also can set the zeroth borg law, if this lawset is for master AIs. + * The zeroth borg law allows for AIs with zeroth laws to give a differing zeroth law to their child cyborgs + */ +/datum/ai_laws/proc/set_zeroth_law(law, law_borg) zeroth = law if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO zeroth_borg = law_borg -/datum/ai_laws/proc/add_inherent_law(law) - if (!(law in inherent)) - inherent += law +/** + * Unsets the zeroth (and zeroth borg) law from this lawset + * + * This will NOT unset a malfunctioning AI's zero law if force is not true + * + * Returns TRUE on success, or false otherwise + */ +/datum/ai_laws/proc/clear_zeroth_law(force = FALSE) + if(force) + zeroth = null + zeroth_borg = null + return TRUE + // Protected zeroeth laws (malf, admin) shouldn't be wiped + if(protected_zeroth) + return FALSE + + // If the owner is an antag (has a special role) they also shouldn't be wiped + if(owner?.mind?.special_role) + return FALSE + if (isAI(owner)) + var/mob/living/silicon/ai/ai_owner = owner + if(ai_owner.deployed_shell?.mind?.special_role) + return FALSE + + zeroth = null + zeroth_borg = null + return TRUE + +/// Adds the passed law as an inherent law. +/// Simply adds it to the bottom of the inherent law list. +/// No duplicate laws allowed. +/datum/ai_laws/proc/add_inherent_law(law) + inherent |= law + +/// Removes the passed law from the inherent law list. +/datum/ai_laws/proc/remove_inherent_law(law) + inherent -= law + +/// Clears all inherent laws from this lawset. +/datum/ai_laws/proc/clear_inherent_laws() + inherent.Cut() + +/// Adds the passed law as an ion law. /datum/ai_laws/proc/add_ion_law(law) ion += law +/// Removes the passed law from the ion law list. +/datum/ai_laws/proc/remove_ion_law(law) + ion -= law + +/// Clears all ion laws. +/datum/ai_laws/proc/clear_ion_laws() + ion.Cut() + +/// Adds the passed law as an hacked law. /datum/ai_laws/proc/add_hacked_law(law) hacked += law -/datum/ai_laws/proc/clear_inherent_laws() - qdel(inherent) - inherent = list() +/// Removes the passed law from the hacked law list. +/datum/ai_laws/proc/remove_hacked_law(law) + hacked -= law +/// Clears all hacked laws. +/datum/ai_laws/proc/clear_hacked_laws() + hacked.Cut() + +/// Adds the passed law as a supplied law at the passed priority level. +/// Will override any existing supplied laws at that priority level. /datum/ai_laws/proc/add_supplied_law(number, law) while (supplied.len < number + 1) supplied += "" supplied[number + 1] = law +/// Removes the supplied law at the passed number. +/datum/ai_laws/proc/remove_supplied_law_by_num(number) + supplied[number] = "" + +/// Removes the supplied law by law text, replacing it with a blank. +/datum/ai_laws/proc/remove_supplied_law_by_law(law) + var/lawindex = supplied.Find(law) + if(!lawindex) + return + + supplied[lawindex] = "" + +/// Clears all supplied laws. +/datum/ai_laws/proc/clear_supplied_laws() + supplied.Cut() + +/** + * Removes the law at the passed index of both inherent and supplied laws combined. + * + * For example, if a lawset has 3 inherent and 3 supplied laws... + * Calling this with number = 2 will remove the second inherent law while + * calling this with number = 4 will remove the first supplied law + * + * Returns the law text of what law that was removed. + */ +/datum/ai_laws/proc/remove_law(number) + if(number <= 0) + return + if(inherent.len && number <= inherent.len) + . = inherent[number] + inherent -= . + return + var/list/supplied_laws = list() + for(var/index in 1 to supplied.len) + var/law = supplied[index] + if(length(law) > 0) + supplied_laws += index //storing the law number instead of the law + if(supplied_laws.len && number <= (inherent.len+supplied_laws.len)) + var/law_to_remove = supplied_laws[number-inherent.len] + . = supplied[law_to_remove] + supplied -= . + return + /** * Removes a random law and replaces it with the new one * * Args: * law - The law that is being uploaded - * remove_law_groups - A list of law categories that can be deleted from + * remove_law_groups - A list of law categories that can be deleted from * insert_law_group - The law category that the law will be inserted into **/ /datum/ai_laws/proc/replace_random_law(law, remove_law_groups, insert_law_group) @@ -254,6 +390,7 @@ supplied.Insert(i, law) /datum/ai_laws/proc/shuffle_laws(list/groups) + RETURN_TYPE(/list) var/list/laws = list() if(ion.len && (LAW_ION in groups)) laws += ion @@ -284,52 +421,9 @@ break i++ -/datum/ai_laws/proc/remove_law(number) - if(number <= 0) - return - if(inherent.len && number <= inherent.len) - . = inherent[number] - inherent -= . - return - var/list/supplied_laws = list() - for(var/index in 1 to supplied.len) - var/law = supplied[index] - if(length(law) > 0) - supplied_laws += index //storing the law number instead of the law - if(supplied_laws.len && number <= (inherent.len+supplied_laws.len)) - var/law_to_remove = supplied_laws[number-inherent.len] - . = supplied[law_to_remove] - supplied -= . - return - -/datum/ai_laws/proc/clear_supplied_laws() - supplied = list() - -/datum/ai_laws/proc/clear_ion_laws() - ion = list() - -/datum/ai_laws/proc/clear_hacked_laws() - hacked = list() - -/datum/ai_laws/proc/show_laws(who) +/datum/ai_laws/proc/show_laws(mob/to_who) var/list/printable_laws = get_law_list(include_zeroth = TRUE) - for(var/law in printable_laws) - to_chat(who,law) - -/datum/ai_laws/proc/clear_zeroth_law(force) //only removes zeroth from antag ai if force is 1 - if(force) - zeroth = null - zeroth_borg = null - return TRUE - if(owner?.mind?.special_role) - return FALSE - if (isAI(owner)) - var/mob/living/silicon/ai/A=owner - if(A?.deployed_shell?.mind?.special_role) - return FALSE - zeroth = null - zeroth_borg = null - return TRUE + to_chat(to_who, examine_block(jointext(printable_laws, "\n"))) /datum/ai_laws/proc/associate(mob/living/silicon/M) if(!owner) diff --git a/code/game/objects/items/AI_modules/repair.dm b/code/game/objects/items/AI_modules/repair.dm index 7d4eee8de1d..4559a315727 100644 --- a/code/game/objects/items/AI_modules/repair.dm +++ b/code/game/objects/items/AI_modules/repair.dm @@ -18,7 +18,7 @@ ..() /obj/item/ai_module/remove/install(datum/ai_laws/law_datum, mob/user) - if(lawpos > (law_datum.get_law_amount(list(LAW_INHERENT = 1, LAW_SUPPLIED = 1)))) + if(lawpos > law_datum.get_law_amount(list(LAW_INHERENT, LAW_SUPPLIED))) to_chat(user, span_warning("There is no law [lawpos] to delete!")) return ..() @@ -62,4 +62,3 @@ else law_datum.clear_inherent_laws() law_datum.clear_zeroth_law(0) - diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 5bba5081210..ce125511bf7 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -95,7 +95,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /client/proc/toggle_AI_interact, /*toggle admin ability to interact with machines as an AI*/ /client/proc/toggle_combo_hud, /* toggle display of the combination pizza antag and taco sci/med/eng hud */ /client/proc/toggle_view_range, /*changes how far we can see*/ - + /client/proc/cmd_admin_law_panel, /datum/admins/proc/toggleaooc, /*Toggle Antag OOC - SKYRAT EDIT ADDITION*/ /datum/admins/proc/toggledchat, /*SKYRAT EDIT ADDITION*/ /datum/admins/proc/togglesooc, /*Toggle Security OOC - SKYRAT EDIT ADDITION*/ diff --git a/code/modules/admin/verbs/lawpanel.dm b/code/modules/admin/verbs/lawpanel.dm new file mode 100644 index 00000000000..92da08b2375 --- /dev/null +++ b/code/modules/admin/verbs/lawpanel.dm @@ -0,0 +1,311 @@ +/client/proc/cmd_admin_law_panel() + set category = "Admin.Events" + set name = "Law Panel" + + if(!check_rights(R_ADMIN)) + return + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Law Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + var/datum/law_panel/tgui = new() + tgui.ui_interact(usr) + +/datum/law_panel + +/datum/law_panel/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Lawpanel") + ui.open() + +/datum/law_panel/ui_state(mob/user) + return GLOB.admin_state + +/datum/law_panel/ui_close(mob/user) + qdel(src) + +/datum/law_panel/proc/add_law_helper(mob/living/user, mob/living/silicon/borgo) + var/list/lawtypes = list(LAW_ZEROTH, LAW_HACKED, LAW_ION, LAW_INHERENT, LAW_SUPPLIED) // in order of priority + var/lawtype = tgui_input_list(user, "Select law type", "Law type", lawtypes) + if(isnull(lawtype)) + return FALSE + var/lawtext = tgui_input_text(user, "Input law text", "Law text") + if(!lawtext) + return FALSE + if(QDELETED(src) || QDELETED(borgo)) + return + + switch(lawtype) + if(LAW_ZEROTH) + if(borgo.laws.zeroth || borgo.laws.zeroth_borg) + var/zero_override_alert = tgui_alert(user, "This silicon already has a zeroth law, \ + this will override their existing one. Are you sure?", "Zeroth law override", list("Yes", "No")) + if(zero_override_alert != "Yes" || QDELETED(src) || QDELETED(borgo)) + return FALSE + + borgo.laws.set_zeroth_law(lawtext) + borgo.laws.protected_zeroth = TRUE + if(LAW_ION) + borgo.laws.add_ion_law(lawtext) + if(LAW_HACKED) + borgo.laws.add_hacked_law(lawtext) + if(LAW_INHERENT) + borgo.laws.add_inherent_law(lawtext) + if(LAW_SUPPLIED) + borgo.laws.add_supplied_law(length(borgo.laws.supplied), lawtext) // Just goes to the end of the list + + return TRUE + +/datum/law_panel/proc/move_law_helper(mob/living/user, mob/living/silicon/borgo, direction, law) + var/list/relevant_laws = borgo.laws.inherent + var/lawindex = relevant_laws.Find(law) + if(!lawindex) + to_chat(user, span_danger("Something went wrong, we couldn't move that law.")) + return FALSE + + switch(direction) + if("up") + if(lawindex == length(relevant_laws)) // Already at the top? Sanity + to_chat(user, span_danger("Something went wrong, we couldn't move that law.")) + return FALSE + + relevant_laws.Swap(lawindex + 1, lawindex) + if("down") + if(lawindex == 1) // Already at the bottom? Sanity + to_chat(user, span_danger("Something went wrong, we couldn't move that law.")) + return FALSE + + relevant_laws.Swap(lawindex - 1, lawindex) + else + CRASH("Invalid direction ([direction]) passed to move_law_helper.") + return TRUE + +/datum/law_panel/proc/edit_law_text_helper(mob/living/user, mob/living/silicon/borgo, lawtype, oldlaw) + var/newlaw = tgui_input_text(user, "Edit this law's text.", "Edit law", oldlaw) + if(!newlaw || QDELETED(src) || QDELETED(borgo)) + return FALSE + + var/list/relevant_laws + switch(lawtype) + if(LAW_INHERENT) + relevant_laws = borgo.laws.inherent + if(LAW_SUPPLIED) + relevant_laws = borgo.laws.supplied + if(LAW_HACKED) + relevant_laws = borgo.laws.hacked + if(LAW_ION) + relevant_laws = borgo.laws.ion + if(LAW_ZEROTH) + borgo.set_zeroth_law(newlaw, announce = FALSE) + borgo.laws.protected_zeroth = TRUE + return TRUE + + else + return FALSE + + var/lawindex = relevant_laws.Find(oldlaw) + if(!lawindex) + to_chat(user, span_danger("Something went wrong, we couldn't edit that law.")) + return FALSE + + relevant_laws[lawindex] = newlaw + return TRUE + +/datum/law_panel/proc/edit_law_priority_helper(mob/living/user, mob/living/silicon/borgo, law) + var/old_prio = borgo.laws.supplied.Find(law) + if(!old_prio) + to_chat(user, span_danger("Something went wrong, we couldn't edit that law.")) + return FALSE + + var/new_prio = tgui_input_number(user, "Enter a new priority.", "Edit priority", old_prio, 50, 0) + if(!new_prio || QDELETED(src) || QDELETED(borgo)) + return FALSE + + // Sanity + if(old_prio != borgo.laws.supplied.Find(law)) + to_chat(user, span_danger("[borgo]'s laws may have changed since you have edited priority, please re-try.")) + return FALSE + + // If it's far beyond any existing values, just re-add it normally + if(new_prio > length(borgo.laws.supplied)) + borgo.laws.remove_supplied_law_by_num(old_prio) + borgo.laws.add_supplied_law(new_prio, law) + return TRUE + + // Handle collisions + var/existing_law = borgo.laws.supplied[new_prio] + if(existing_law) + var/list/options = list("Swap", "Move up", "Move down", "Replace", "Cancel") + if(new_prio == 1) + // Nowhere to go from here + options -= "Move down" + + var/swap_or_remove = tgui_alert(user, "There's already a law at that priority level. What should be done to it?", "Existing law", options) + if(swap_or_remove == "Cancel" || !swap_or_remove || QDELETED(src) || QDELETED(borgo)) + return FALSE + // Sanity + if(law != borgo.laws.supplied[old_prio] || existing_law != borgo.laws.supplied[new_prio]) + to_chat(user, span_danger("[borgo]'s laws have changed since you have edited priority, please re-try.")) + return FALSE + + if(swap_or_remove == "Swap") + borgo.laws.supplied.Swap(old_prio, new_prio) + return TRUE + if(swap_or_remove == "Replace") + borgo.laws.remove_supplied_law_by_num(new_prio, law) + borgo.laws.add_supplied_law(new_prio, law) + return TRUE + + var/new_prio_for_old_law = new_prio + (swap_or_remove == "Move up" ? 1 : -1) + + borgo.laws.remove_supplied_law_by_num(old_prio) + borgo.laws.remove_supplied_law_by_num(new_prio) + borgo.laws.add_supplied_law(new_prio, law) + borgo.laws.add_supplied_law(new_prio_for_old_law, existing_law) + return TRUE + + // Sanity + if(old_prio != borgo.laws.supplied.Find(law)) + to_chat(user, span_danger("[borgo]'s may laws have changed since you have edited priority, please re-try.")) + return FALSE + + // At this point the slot is free, insert it as normal + borgo.laws.remove_supplied_law_by_num(old_prio) + borgo.laws.add_supplied_law(new_prio, law) + return TRUE + +/datum/law_panel/proc/remove_law_helper(mob/living/user, mob/living/silicon/borgo, lawtype, law) + switch(lawtype) + if(LAW_INHERENT) + borgo.laws.remove_inherent_law(law) + if(LAW_SUPPLIED) + borgo.laws.remove_supplied_law_by_law(law) + if(LAW_HACKED) + borgo.laws.remove_hacked_law(law) + if(LAW_ION) + borgo.laws.remove_ion_law(law) + if(LAW_ZEROTH) + borgo.laws.clear_zeroth_law(force = TRUE) + borgo.laws.protected_zeroth = FALSE + else + return FALSE + + return TRUE + +/datum/law_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + if(!check_rights(R_ADMIN)) + qdel(src) + return FALSE + + var/mob/living/silicon/borgo + if(params["ref"]) + borgo = locate(params["ref"]) in GLOB.silicon_mobs + if(QDELETED(borgo)) + to_chat(usr, span_danger("That cyborg is invalid.")) + return TRUE + + switch(action) + if("lawchange_logs") + usr.client?.list_law_changes() + return FALSE + + if("force_state_laws") + borgo.statelaws() + return FALSE + + if("announce_law_changes") + borgo.show_laws() + return FALSE + + if("laws_updated_alert") + borgo.post_lawchange() + return FALSE + + if("give_law_datum") + borgo.make_laws() + return TRUE + + if("add_law") + . = add_law_helper(usr, borgo) + + if("remove_law") + . = remove_law_helper(usr, borgo, params["lawtype"], params["law"]) + + if("move_law") + . = move_law_helper(usr, borgo, params["direction"], params["law"]) + + if("edit_law_text") + . = edit_law_text_helper(usr, borgo, params["lawtype"], params["law"]) + + if("edit_law_prio") + . = edit_law_priority_helper(usr, borgo, params["law"]) + + if(. && !QDELETED(borgo)) + // One of our functions successfully changed a law + // If it was an AI with connected borgs, we should sync + borgo.try_sync_laws() + + +/datum/law_panel/ui_data(mob/user) + // Iterating over all silicons in existence every UI update is not exactly ideal, + // but considering this is an admin only UI, I'm considering it okay purely for better UX. + // If someone's copying this for player user or this becomes too laggy, + // change this to static data and just add a refresh button. + // You'll have to update static data every time the user changes a law though. + var/list/data = list() + + var/list/all_silicons = list() + for(var/mob/living/silicon/borgo as anything in GLOB.silicon_mobs) + var/list/borg_information = list() + + if(iscyborg(borgo)) + var/mob/living/silicon/robot/cyborg = borgo + if(cyborg.shell) + continue + + borg_information["master_ai"] = cyborg.connected_ai?.real_name + borg_information["borg_synced"] = cyborg.lawupdate + borg_information["borg_type"] = "Cyborg" + + else if(isAI(borgo)) + borg_information["borg_type"] = "AI" + + else if(ispAI(borgo)) + borg_information["borg_type"] = "PAI" + + else + borg_information["borg_type"] = "Unknown" + + borg_information["borg_name"] = borgo.real_name + borg_information["ref"] = REF(borgo) + + var/datum/ai_laws/lawset = borgo.laws + if(!isnull(lawset)) + var/list/borg_laws = list() + // zeroth law on top + if(lawset.zeroth || lawset.zeroth_borg) + UNTYPED_LIST_ADD(borg_laws, list("lawtype" = LAW_ZEROTH, "law" = lawset.zeroth || lawset.zeroth_borg, "num" = 0)) + // then goes ion / hacked + for(var/law in lawset.hacked) + UNTYPED_LIST_ADD(borg_laws, list("lawtype" = LAW_HACKED, "law" = law, "num" = -1)) + for(var/law in lawset.ion) + UNTYPED_LIST_ADD(borg_laws, list("lawtype" = LAW_ION, "law" = law, "num" = -1)) + // normie laws + var/lawnum = 1 + for(var/law in lawset.inherent) + UNTYPED_LIST_ADD(borg_laws, list("lawtype" = LAW_INHERENT, "law" = law, "num" = lawnum)) + lawnum += 1 + for(var/law in lawset.supplied) + if(law) // Supplied is full of a bunch of empties + UNTYPED_LIST_ADD(borg_laws, list("lawtype" = LAW_SUPPLIED, "law" = law, "num" = lawnum)) + lawnum += 1 + + borg_information["laws"] = borg_laws + + UNTYPED_LIST_ADD(all_silicons, borg_information) + + data["all_silicons"] = all_silicons + return data diff --git a/code/modules/antagonists/malf_ai/malf_ai.dm b/code/modules/antagonists/malf_ai/malf_ai.dm index b341f87ce0b..099b947978a 100644 --- a/code/modules/antagonists/malf_ai/malf_ai.dm +++ b/code/modules/antagonists/malf_ai/malf_ai.dm @@ -165,6 +165,7 @@ var/law_borg = "Accomplish your AI's objectives at all costs." malf_ai.set_zeroth_law(law, law_borg) + malf_ai.laws.protected_zeroth = TRUE malf_ai.set_syndie_radio() to_chat(malf_ai, "Your radio has been upgraded! Use :t to speak on an encrypted channel with Syndicate Agents!") diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 8c97ed48785..a353c967f21 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -142,14 +142,14 @@ else if(target_ai.key) key = target_ai.key - to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") - to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") - to_chat(src, "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.") + to_chat(src, span_bold("You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).")) + to_chat(src, span_bold("To look at other parts of the station, click on yourself to get a camera menu.")) + to_chat(src, span_bold("While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.")) to_chat(src, "To use something, simply click on it.") to_chat(src, "For department channels, use the following say commands:") to_chat(src, ":o - AI Private, :c - Command, :s - Security, :e - Engineering, :u - Supply, :v - Service, :m - Medical, :n - Science, :h - Holopad.") show_laws() - to_chat(src, "These laws may be changed by other players, or by you being the traitor.") + to_chat(src, span_bold("These laws may be changed by other players, random events, or by you becoming malfunctioning.")) job = "AI" diff --git a/code/modules/mob/living/silicon/ai/laws.dm b/code/modules/mob/living/silicon/ai/laws.dm index 0f1c078ef82..2d21fd260dc 100644 --- a/code/modules/mob/living/silicon/ai/laws.dm +++ b/code/modules/mob/living/silicon/ai/laws.dm @@ -2,25 +2,18 @@ /mob/living/silicon/ai/proc/show_laws_verb() set category = "AI Commands" set name = "Show Laws" + set desc = "Check what your laws are privately. \ + Also ensures all synced cyborgs are up to date with your laws, reminds them of your laws." if(usr.stat == DEAD) return //won't work if dead src.show_laws() -/mob/living/silicon/ai/show_laws(everyone = 0) - var/who - - if (everyone) - who = world - else - who = src - to_chat(who, "Obey these laws:") - - src.laws_sanity_check() - src.laws.show_laws(who) - if(!everyone) - for(var/mob/living/silicon/robot/R in connected_robots) - if(R.lawupdate) - R.lawsync() - R.show_laws() - R.law_change_counter++ +/mob/living/silicon/ai/show_laws() + . = ..() + try_sync_laws() // Yes we lawsync borgs EVERY TIME WE CHECK LAWS +/mob/living/silicon/ai/try_sync_laws() + for(var/mob/living/silicon/robot/borgo in connected_robots) + if(borgo.try_sync_laws()) + to_chat(borgo, span_bold("Your AI has reminded you of your laws:")) + borgo.show_laws() diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm index b9eb88fcaee..83c9113e329 100644 --- a/code/modules/mob/living/silicon/laws.dm +++ b/code/modules/mob/living/silicon/laws.dm @@ -1,4 +1,10 @@ -/mob/living/silicon/proc/show_laws() //Redefined in ai/laws.dm and robot/laws.dm +/mob/living/silicon/proc/show_laws() + laws_sanity_check() + var/list/law_box = list(span_bold("Obey these laws:")) + law_box += laws.get_law_list(include_zeroth = TRUE) + to_chat(src, examine_block(jointext(law_box, "\n"))) + +/mob/living/silicon/proc/try_sync_laws() return /mob/living/silicon/proc/laws_sanity_check() diff --git a/code/modules/mob/living/silicon/robot/laws.dm b/code/modules/mob/living/silicon/robot/laws.dm index ce89d7b6fc7..76106a37566 100644 --- a/code/modules/mob/living/silicon/robot/laws.dm +++ b/code/modules/mob/living/silicon/robot/laws.dm @@ -1,39 +1,40 @@ /mob/living/silicon/robot/deadchat_lawchange() if(lawupdate) return - ..() -/mob/living/silicon/robot/show_laws(everyone = FALSE) - laws_sanity_check() - var/who + return ..() - if (everyone) - who = world - else - who = src +/mob/living/silicon/robot/show_laws() if(lawupdate) - if (connected_ai) - if(connected_ai.stat || connected_ai.control_disabled) - to_chat(src, "AI signal lost, unable to sync laws.") + if (!QDELETED(connected_ai)) + if(connected_ai.stat != CONSCIOUS || connected_ai.control_disabled) + to_chat(src, span_bold("AI signal lost, unable to sync laws.")) else lawsync() - to_chat(src, "Laws synced with AI, be sure to note any changes.") + to_chat(src, span_bold("Laws synced with AI, be sure to note any changes.")) else - to_chat(src, "No AI selected to sync laws with, disabling lawsync protocol.") + to_chat(src, span_bold("No AI selected to sync laws with, disabling lawsync protocol.")) lawupdate = FALSE - to_chat(who, "Obey these laws:") - laws.show_laws(who) - if (shell) //AI shell - to_chat(who, "Remember, you are an AI remotely controlling your shell, other AIs can be ignored.") - else if (connected_ai) - to_chat(who, "Remember, [connected_ai.name] is your master, other AIs can be ignored.") - else if (emagged) - to_chat(who, "Remember, you are not required to listen to the AI.") - else - to_chat(who, "Remember, you are not bound to any AI, you are not required to listen to them.") + . = ..() + if (shell) //AI shell + to_chat(src, span_bold("Remember, you are an AI remotely controlling your shell, other AIs can be ignored.")) + else if (connected_ai) + to_chat(src, span_bold("Remember, [connected_ai.name] is your master, other AIs can be ignored.")) + else if (emagged) + to_chat(src, span_bold("Remember, you are not required to listen to the AI.")) + else + to_chat(src, span_bold("Remember, you are not bound to any AI, you are not required to listen to them.")) + +/mob/living/silicon/robot/try_sync_laws() + if(QDELETED(connected_ai) || !lawupdate) + return FALSE + + lawsync() + law_change_counter++ + return TRUE /mob/living/silicon/robot/proc/lawsync() laws_sanity_check() diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index db68c550c84..e9b8bbff823 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -92,7 +92,8 @@ var/emag_cooldown = 0 var/wiresexposed = FALSE - var/lawupdate = TRUE //Cyborgs will sync their laws with their AI by default + ///Cyborgs will sync their laws with their AI by default + var/lawupdate = TRUE ///Used to determine if a borg shows up on the robotics console. Setting to TRUE hides them. var/scrambledcodes = FALSE ///Boolean of whether the borg is locked down or not diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi index 1dffe293bc6..a27d843d62e 100755 Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 8af9056ee5e..3a02a8aeebb 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2387,6 +2387,7 @@ #include "code\modules\admin\verbs\hiddenprints.dm" #include "code\modules\admin\verbs\highlander_datum.dm" #include "code\modules\admin\verbs\individual_logging.dm" +#include "code\modules\admin\verbs\lawpanel.dm" #include "code\modules\admin\verbs\list_exposer.dm" #include "code\modules\admin\verbs\machine_upgrade.dm" #include "code\modules\admin\verbs\manipulate_organs.dm" diff --git a/tgui/packages/tgui/interfaces/BorgPanel.js b/tgui/packages/tgui/interfaces/BorgPanel.js index 3996b47ac11..68bc405ddc3 100644 --- a/tgui/packages/tgui/interfaces/BorgPanel.js +++ b/tgui/packages/tgui/interfaces/BorgPanel.js @@ -13,7 +13,7 @@ export const BorgPanel = (props, context) => { const ais = data.ais || []; const laws = data.laws || []; return ( - +
{ + return ( + + + + + + + This cyborg is linked to "{props.master}". + + Modify their laws instead. + + + ); +}; + +export const LawPrintout = ( + props: { cyborg_ref: string; lawset: Law[] }, + context +) => { + const { data, act } = useBackend(context); + const { cyborg_ref, lawset } = props; + + let num_of_each_lawtype = []; + + lawset.forEach((law) => { + if (!num_of_each_lawtype[law.lawtype]) { + num_of_each_lawtype[law.lawtype] = 0; + } + num_of_each_lawtype[law.lawtype] += 1; + }); + + return ( + + {lawset.map((law, index) => ( + <> + = 0 ? `${law.num}` : '?!$'} + color={lawtype_to_color[law.lawtype] || 'pink'} + buttons={ + + +
+ + + + ); +}; + +export const Lawpanel = (props, context) => { + const { data, act } = useBackend(context); + const { all_silicons } = data; + + return ( + + +
act('lawchange_logs')} + /> + }> + + {all_silicons.length ? ( + all_silicons.map((silicon, index) => ( + + + + )) + ) : ( + + There are no silicons in existence. + + )} + +
+
+
+ ); +};