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={
+
+
+
+
+
+
+ act('remove_law', {
+ ref: cyborg_ref,
+ law: law.law,
+ lawtype: law.lawtype,
+ })
+ }
+ />
+
+
+
+ {law.lawtype === 'inherent' && (
+ <>
+
+
+
+
+ >
+ )}
+ {law.lawtype === 'supplied' && (
+
+
+ )}
+
+ }>
+ {law.law}
+
+
+ >
+ ))}
+
+
+
+
+ );
+};
+
+export const SiliconReadout = (props: { cyborg: Silicon }, context) => {
+ const { data, act } = useBackend(context);
+ const { cyborg } = props;
+
+ return (
+
+
+
+
+ {cyborg.master_ai && cyborg.borg_synced && (
+
+ )}
+
+
+ {cyborg.laws === null ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+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.
+
+ )}
+
+
+
+
+ );
+};