diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm
index 8a8d13de6be..fcda2a03d94 100644
--- a/code/_helpers/lists.dm
+++ b/code/_helpers/lists.dm
@@ -71,11 +71,17 @@ proc/isemptylist(list/list)
return 1
return 0
-//Empties the list by setting the length to 0. Hopefully the elements get garbage collected
-proc/clearlist(list/list)
- if(istype(list))
- list.len = 0
- return
+/proc/instances_of_type_in_list(var/atom/A, var/list/L)
+ var/instances = 0
+ for(var/type in L)
+ if(istype(A, type))
+ instances++
+ return instances
+
+//Empties the list by .Cut(). Setting lenght = 0 has been confirmed to leak references.
+proc/clearlist(var/list/L)
+ if(islist(L))
+ L.Cut()
//Removes any null entries from the list
proc/listclearnulls(list/list)
diff --git a/code/game/antagonist/antagonist.dm b/code/game/antagonist/antagonist.dm
index faf7526a9cb..011ae5ebe6c 100644
--- a/code/game/antagonist/antagonist.dm
+++ b/code/game/antagonist/antagonist.dm
@@ -86,7 +86,8 @@
/datum/antagonist/proc/tick()
return 1
-/datum/antagonist/proc/get_candidates(var/ghosts_only)
+// Get the raw list of potential players.
+/datum/antagonist/proc/build_candidate_list(var/ghosts_only)
candidates = list() // Clear.
// Prune restricted status. Broke it up for readability.
@@ -108,46 +109,57 @@
return candidates
/datum/antagonist/proc/attempt_random_spawn()
+ update_current_antag_max()
build_candidate_list(flags & (ANTAG_OVERRIDE_MOB|ANTAG_OVERRIDE_JOB))
attempt_spawn()
finalize_spawn()
-/datum/antagonist/proc/attempt_late_spawn(var/datum/mind/player)
+/datum/antagonist/proc/attempt_auto_spawn()
if(!can_late_spawn())
return 0
- if(!istype(player))
- var/list/players = get_candidates(is_latejoin_template())
- if(players && players.len)
- player = pick(players)
- if(!istype(player))
- message_admins("AUTO[uppertext(ticker.mode.name)]: Failed to find a candidate for [role_text].")
- return 0
- player.current << "You have been selected this round as an antagonist!"
- message_admins("AUTO[uppertext(ticker.mode.name)]: Selected [player] as a [role_text].")
- if(istype(player.current, /mob/dead))
- create_default(player.current)
- else
- add_antagonist(player,0,0,0,1,1)
- return 1
-/datum/antagonist/proc/build_candidate_list(var/ghosts_only)
- // Get the raw list of potential players.
update_current_antag_max()
- candidates = get_candidates(ghosts_only)
+ var/active_antags = get_active_antag_count()
+ log_debug("[uppertext(id)]: Found [active_antags]/[cur_max] active [role_text_plural].")
+
+ if(active_antags >= cur_max)
+ log_debug("Could not auto-spawn a [role_text], active antag limit reached.")
+ return 0
+
+ build_candidate_list(flags & (ANTAG_OVERRIDE_MOB|ANTAG_OVERRIDE_JOB))
+ if(!candidates.len)
+ log_debug("Could not auto-spawn a [role_text], no candidates found.")
+ return 0
+
+ attempt_spawn(1) //auto-spawn antags one at a time
+ if(!pending_antagonists.len)
+ log_debug("Could not auto-spawn a [role_text], none of the available candidates could be selected.")
+ return 0
+
+ var/datum/mind/player = pending_antagonists[1]
+ if(!add_antagonist(player,0,0,0,1,1))
+ log_debug("Could not auto-spawn a [role_text], failed to add antagonist.")
+ return 0
+
+ reset_antag_selection()
+
+ return 1
//Selects players that will be spawned in the antagonist role from the potential candidates
//Selected players are added to the pending_antagonists lists.
//Attempting to spawn an antag role with ANTAG_OVERRIDE_JOB should be done before jobs are assigned,
//so that they do not occupy regular job slots. All other antag roles should be spawned after jobs are
//assigned, so that job restrictions can be respected.
-/datum/antagonist/proc/attempt_spawn(var/rebuild_candidates = 1)
+/datum/antagonist/proc/attempt_spawn(var/spawn_target = null)
+ if(spawn_target == null)
+ spawn_target = initial_spawn_target
// Update our boundaries.
if(!candidates.len)
return 0
//Grab candidates randomly until we have enough.
- while(candidates.len && pending_antagonists.len < initial_spawn_target)
+ while(candidates.len && pending_antagonists.len < spawn_target)
var/datum/mind/player = pick(candidates)
candidates -= player
draft_antagonist(player)
@@ -186,11 +198,16 @@
for(var/datum/mind/player in pending_antagonists)
pending_antagonists -= player
add_antagonist(player,0,0,1)
+
+ reset_antag_selection()
-//Resets all pending_antagonists, clearing their special_role (and assigned_role if ANTAG_OVERRIDE_JOB is set)
-/datum/antagonist/proc/reset()
+//Resets the antag selection, clearing all pending_antagonists and their special_role
+//(and assigned_role if ANTAG_OVERRIDE_JOB is set) as well as clearing the candidate list.
+//Existing antagonists are left untouched.
+/datum/antagonist/proc/reset_antag_selection()
for(var/datum/mind/player in pending_antagonists)
if(flags & ANTAG_OVERRIDE_JOB)
player.assigned_role = null
player.special_role = null
pending_antagonists.Cut()
+ candidates.Cut()
diff --git a/code/game/antagonist/antagonist_create.dm b/code/game/antagonist/antagonist_create.dm
index 072dd88bee4..bbae60800ef 100644
--- a/code/game/antagonist/antagonist_create.dm
+++ b/code/game/antagonist/antagonist_create.dm
@@ -16,7 +16,8 @@
create_objectives(target)
update_icons_added(target)
greet(target)
- announce_antagonist_spawn()
+ if(!gag_announcement)
+ announce_antagonist_spawn()
/datum/antagonist/proc/create_default(var/mob/source)
var/mob/living/M
diff --git a/code/game/antagonist/antagonist_helpers.dm b/code/game/antagonist/antagonist_helpers.dm
index 056fbd18651..adc706734fd 100644
--- a/code/game/antagonist/antagonist_helpers.dm
+++ b/code/game/antagonist/antagonist_helpers.dm
@@ -20,6 +20,17 @@
/datum/antagonist/proc/get_antag_count()
return current_antagonists ? current_antagonists.len : 0
+/datum/antagonist/proc/get_active_antag_count()
+ var/active_antags = 0
+ for(var/datum/mind/player in current_antagonists)
+ var/mob/living/L = player.current
+ if(!L || L.stat == DEAD)
+ continue //no mob or dead
+ if(!L.client && !L.teleop)
+ continue //SSD
+ active_antags++
+ return active_antags
+
/datum/antagonist/proc/is_antagonist(var/datum/mind/player)
if(player in current_antagonists)
return 1
@@ -33,9 +44,6 @@
return (flags & ANTAG_VOTABLE)
/datum/antagonist/proc/can_late_spawn()
- update_current_antag_max()
- if(get_antag_count() >= cur_max)
- return 0
return 1
/datum/antagonist/proc/is_latejoin_template()
diff --git a/code/game/antagonist/station/rogue_ai.dm b/code/game/antagonist/station/rogue_ai.dm
index c17b6c0f86b..4240708cc22 100644
--- a/code/game/antagonist/station/rogue_ai.dm
+++ b/code/game/antagonist/station/rogue_ai.dm
@@ -22,13 +22,11 @@ var/datum/antagonist/rogue_ai/malf
malf = src
-/datum/antagonist/rogue_ai/get_candidates()
+/datum/antagonist/rogue_ai/build_candidate_list()
..()
for(var/datum/mind/player in candidates)
if(player.assigned_role && player.assigned_role != "AI")
candidates -= player
- if(!candidates.len)
- return list()
return candidates
diff --git a/code/game/gamemodes/calamity/calamity.dm b/code/game/gamemodes/calamity/calamity.dm
index 4f7b9ab49de..3104e624e34 100644
--- a/code/game/gamemodes/calamity/calamity.dm
+++ b/code/game/gamemodes/calamity/calamity.dm
@@ -12,14 +12,15 @@
/datum/game_mode/calamity/create_antagonists()
- shuffle(all_antag_types) // This is probably the only instance in the game where the order will be important.
- var/i = 1
+ //Let's not modify global lists for trivial reasons, even if it seems harmless right now.
+ var/list/antag_candidates = all_antag_types.Copy()
+
var/grab_antags = round(num_players()/ANTAG_TYPE_RATIO)+1
- for(var/antag_id in all_antag_types)
- if(i > grab_antags)
- break
+ while(antag_candidates.len && antag_tags.len < grab_antags)
+ var/antag_id = pick(antag_candidates)
+ antag_candidates -= antag_id
antag_tags |= antag_id
- i++
+
..()
/datum/game_mode/calamity/check_victory()
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index aa16a9b4d15..881dea4dbd4 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -21,7 +21,6 @@ var/global/list/additional_antag_types = list()
var/list/antag_tags = list() // Core antag templates to spawn.
var/list/antag_templates // Extra antagonist types to include.
- var/list/latejoin_templates = list()
var/round_autoantag = 0 // Will this round attempt to periodically spawn more antagonists?
var/antag_scaling_coeff = 5 // Coefficient for scaling max antagonists to player count.
var/require_all_templates = 0 // Will only start if all templates are checked and can spawn.
@@ -183,6 +182,7 @@ var/global/list/additional_antag_types = list()
/datum/game_mode/proc/pre_setup()
for(var/datum/antagonist/antag in antag_templates)
+ antag.update_current_antag_max()
antag.build_candidate_list() //compile a list of all eligible candidates
//antag roles that replace jobs need to be assigned before the job controller hands out jobs.
@@ -207,8 +207,6 @@ var/global/list/additional_antag_types = list()
if(!(antag.flags & ANTAG_OVERRIDE_JOB))
antag.attempt_spawn() //select antags to be spawned
antag.finalize_spawn() //actually spawn antags
- if(antag.is_latejoin_template())
- latejoin_templates |= antag
if(emergency_shuttle && auto_recall_shuttle)
emergency_shuttle.auto_recall = 1
@@ -221,7 +219,7 @@ var/global/list/additional_antag_types = list()
/datum/game_mode/proc/fail_setup()
for(var/datum/antagonist/antag in antag_templates)
- antag.reset()
+ antag.reset_antag_selection()
/datum/game_mode/proc/announce_ert_disabled()
if(!ert_disabled)
@@ -493,6 +491,7 @@ var/global/list/additional_antag_types = list()
if(antag)
antag_templates |= antag
+ shuffle(antag_templates) //In the case of multiple antag types
newscaster_announcements = pick(newscaster_standard_feeds)
/datum/game_mode/proc/check_victory()
diff --git a/code/game/gamemodes/game_mode_latespawn.dm b/code/game/gamemodes/game_mode_latespawn.dm
index 038cf351ab8..9c79a689251 100644
--- a/code/game/gamemodes/game_mode_latespawn.dm
+++ b/code/game/gamemodes/game_mode_latespawn.dm
@@ -3,55 +3,39 @@
/datum/game_mode/var/max_autotraitor_delay = 12000 // Approx 20 minutes.
/datum/game_mode/var/process_count = 0
-/datum/game_mode/proc/get_usable_templates(var/list/supplied_templates)
- var/list/usable_templates = list()
- for(var/datum/antagonist/A in supplied_templates)
- if(A.can_late_spawn())
- message_admins("AUTO[uppertext(name)]: [A.id] selected for spawn attempt.")
- usable_templates |= A
- return usable_templates
-
///process()
///Called by the gameticker
/datum/game_mode/proc/process()
- // Slow this down a bit so latejoiners have a chance of being antags.
- process_count++
- if(process_count >= 10)
- process_count = 0
- try_latespawn()
+ if(round_autoantag && world.time < next_spawn && !emergency_shuttle.departed)
+ process_autoantag()
-/datum/game_mode/proc/latespawn(var/mob/living/carbon/human/character)
- if(!character.mind)
- return
- try_latespawn(character.mind)
+//This can be overriden in case a game mode needs to do stuff when a player latejoins
+/datum/game_mode/proc/handle_latejoin(var/mob/living/carbon/human/character)
return 0
-/datum/game_mode/proc/try_latespawn(var/datum/mind/player, var/latejoin_only)
+/datum/game_mode/proc/process_autoantag()
+ message_admins("[uppertext(name)]: Attempting autospawn.")
- if(emergency_shuttle.departed || !round_autoantag)
- return
-
- if(world.time < next_spawn)
- return
-
- message_admins("AUTO[uppertext(name)]: Attempting spawn.")
-
- var/list/usable_templates
- if(latejoin_only && latejoin_templates.len)
- usable_templates = get_usable_templates(latejoin_templates)
- else if (antag_templates && antag_templates.len)
- usable_templates = get_usable_templates(antag_templates)
- else
- message_admins("AUTO[uppertext(name)]: Failed to find configured mode spawn templates, please disable auto-antagonists until one is added.")
+ var/list/usable_templates = list()
+ for(var/datum/antagonist/A in antag_templates)
+ if(A.can_late_spawn())
+ message_admins("[uppertext(name)]: [A.id] selected for spawn attempt.")
+ usable_templates |= A
+
+ if(!usable_templates.len)
+ message_admins("[uppertext(name)]: Failed to find configured mode spawn templates, please re-enable auto-antagonists after one is added.")
round_autoantag = 0
return
-
+
while(usable_templates.len)
var/datum/antagonist/spawn_antag = pick(usable_templates)
usable_templates -= spawn_antag
- if(spawn_antag.attempt_late_spawn(player))
- message_admins("AUTO[uppertext(name)]: Attempting to latespawn [spawn_antag.id]. ([spawn_antag.get_antag_count()]/[spawn_antag.cur_max])")
+
+ if(spawn_antag.attempt_auto_spawn())
+ message_admins("[uppertext(name)]: Auto-added a new [spawn_antag.role_text].")
+ message_admins("There are now [spawn_antag.get_active_antag_count()]/[spawn_antag.cur_max] active [spawn_antag.role_text_plural].")
next_spawn = world.time + rand(min_autotraitor_delay, max_autotraitor_delay)
return
- message_admins("AUTO[uppertext(name)]: Failed to proc a viable spawn template.")
- next_spawn = world.time + rand(min_autotraitor_delay, max_autotraitor_delay)
+
+ message_admins("[uppertext(name)]: Failed to proc a viable spawn template.")
+ next_spawn = world.time + min_autotraitor_delay //recheck again in the miniumum time
diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm
index 455c6256a4f..6d69941b813 100644
--- a/code/game/machinery/machinery.dm
+++ b/code/game/machinery/machinery.dm
@@ -191,7 +191,10 @@ Class Procs:
return (stat & (NOPOWER|BROKEN|additional_flags))
/obj/machinery/CanUseTopic(var/mob/user)
- if(!interact_offline && (stat & (NOPOWER|BROKEN)))
+ if(stat & BROKEN)
+ return STATUS_CLOSE
+
+ if(!interact_offline && (stat & NOPOWER))
return STATUS_CLOSE
return ..()
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 9ee23b2fee9..ebd5a8a4373 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -722,7 +722,8 @@ var/global/list/obj/item/device/pda/PDAs = list()
if("Message")
var/obj/item/device/pda/P = locate(href_list["target"])
- src.create_message(U, P, !href_list["notap"])
+ var/tap = istype(U, /mob/living/carbon)
+ src.create_message(U, P, tap)
if(mode == 2)
if(href_list["target"] in conversations) // Need to make sure the message went through, if not welp.
active_conversation = href_list["target"]
@@ -1041,7 +1042,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
new_message(sending_device, sending_device.owner, sending_device.ownjob, message)
/obj/item/device/pda/proc/new_message(var/sending_unit, var/sender, var/sender_job, var/message)
- var/reception_message = "\icon[src] Message from [sender] ([sender_job]), \"[message]\" (Reply)"
+ var/reception_message = "\icon[src] Message from [sender] ([sender_job]), \"[message]\" (Reply)"
new_info(message_silent, ttone, reception_message)
log_pda("[usr] (PDA: [sending_unit]) sent \"[message]\" to [name]")
@@ -1053,7 +1054,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
if(ismob(sending_unit.loc) && isAI(loc))
track = "(Follow)"
- var/reception_message = "\icon[src] Message from [sender] ([sender_job]), \"[message]\" (Reply) [track]"
+ var/reception_message = "\icon[src] Message from [sender] ([sender_job]), \"[message]\" (Reply) [track]"
new_info(message_silent, newstone, reception_message)
log_pda("[usr] (PDA: [sending_unit]) sent \"[message]\" to [name]")
diff --git a/code/game/objects/items/weapons/storage/boxes.dm b/code/game/objects/items/weapons/storage/boxes.dm
index 92077107780..f37cdb4dc2c 100644
--- a/code/game/objects/items/weapons/storage/boxes.dm
+++ b/code/game/objects/items/weapons/storage/boxes.dm
@@ -623,35 +623,36 @@
icon_state = "light"
desc = "This box is shaped on the inside so that only light tubes and bulbs fit."
item_state = "syringe_kit"
- storage_slots=21
- can_hold = list(/obj/item/weapon/light/tube, /obj/item/weapon/light/bulb)
- max_storage_space = 42 //holds 21 items of w_class 2
use_to_pickup = 1 // for picking up broken bulbs, not that most people will try
+
+/obj/item/weapon/storage/box/lights/New()
+ ..()
+ make_exact_fit()
/obj/item/weapon/storage/box/lights/bulbs/New()
- ..()
for(var/i = 0; i < 21; i++)
new /obj/item/weapon/light/bulb(src)
+ ..()
/obj/item/weapon/storage/box/lights/tubes
name = "box of replacement tubes"
icon_state = "lighttube"
/obj/item/weapon/storage/box/lights/tubes/New()
- ..()
for(var/i = 0; i < 21; i++)
new /obj/item/weapon/light/tube(src)
+ ..()
/obj/item/weapon/storage/box/lights/mixed
name = "box of replacement lights"
icon_state = "lightmixed"
/obj/item/weapon/storage/box/lights/mixed/New()
- ..()
for(var/i = 0; i < 14; i++)
new /obj/item/weapon/light/tube(src)
for(var/i = 0; i < 7; i++)
new /obj/item/weapon/light/bulb(src)
+ ..()
/obj/item/weapon/storage/box/freezer
name = "portable freezer"
diff --git a/code/game/objects/items/weapons/storage/firstaid.dm b/code/game/objects/items/weapons/storage/firstaid.dm
index c23dfcdb516..a76fb4f1a1f 100644
--- a/code/game/objects/items/weapons/storage/firstaid.dm
+++ b/code/game/objects/items/weapons/storage/firstaid.dm
@@ -130,8 +130,7 @@
/obj/item/weapon/storage/firstaid/surgery
name = "surgery kit"
- desc = "Contains tools for surgery."
- storage_slots = 10
+ desc = "Contains tools for surgery. Has precise foam fitting for safe transport."
/obj/item/weapon/storage/firstaid/surgery/New()
..()
@@ -146,7 +145,8 @@
new /obj/item/weapon/bonegel(src)
new /obj/item/weapon/FixOVein(src)
new /obj/item/stack/medical/advanced/bruise_pack(src)
- return
+
+ make_exact_fit()
/*
* Pill Bottles
diff --git a/code/game/objects/items/weapons/storage/storage.dm b/code/game/objects/items/weapons/storage/storage.dm
index 78665633cfd..70caa6ba36f 100644
--- a/code/game/objects/items/weapons/storage/storage.dm
+++ b/code/game/objects/items/weapons/storage/storage.dm
@@ -235,12 +235,16 @@
usr << "[src] is full, make some space."
return 0 //Storage item is full
- if(can_hold.len && !is_type_in_list(W, can_hold))
- if(!stop_messages)
- if (istype(W, /obj/item/weapon/hand_labeler))
- return 0
- usr << "[src] cannot hold [W]."
- return 0
+ if(can_hold.len)
+ if(!is_type_in_list(W, can_hold))
+ if(!stop_messages && ! istype(W, /obj/item/weapon/hand_labeler))
+ usr << "[src] cannot hold \the [W]."
+ return 0
+ var/max_instances = can_hold[W.type]
+ if(max_instances && instances_of_type_in_list(W, contents) >= max_instances)
+ if(!stop_messages && !istype(W, /obj/item/weapon/hand_labeler))
+ usr << "[src] has no more space specifically for \the [W]."
+ return 0
if(cant_hold.len && is_type_in_list(W, cant_hold))
if(!stop_messages)
@@ -461,6 +465,17 @@
src.quick_empty()
return 1
+/obj/item/weapon/storage/proc/make_exact_fit()
+ storage_slots = contents.len
+
+ can_hold.Cut()
+ max_w_class = 0
+ max_storage_space = 0
+ for(var/obj/item/I in src)
+ can_hold[I.type]++
+ max_w_class = max(I.w_class, max_w_class)
+ max_storage_space += I.get_storage_cost()
+
//Returns the storage depth of an atom. This is the number of storage items the atom is contained in before reaching toplevel (the area).
//Returns -1 if the atom was not found on container.
/atom/proc/storage_depth(atom/container)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index d7024147d07..32b6bde1d42 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -1293,7 +1293,7 @@ proc/admin_notice(var/message, var/rights)
var/datum/antagonist/antag = all_antag_types[antag_type]
message_admins("[key_name(usr)] attempting to force latespawn with template [antag.id].")
- antag.attempt_late_spawn()
+ antag.attempt_auto_spawn()
/datum/admins/proc/force_mode_latespawn()
set category = "Admin"
@@ -1310,6 +1310,5 @@ proc/admin_notice(var/message, var/rights)
usr << "Mode has not started."
return
- message_admins("[key_name(usr)] attempting to force mode latespawn.")
- ticker.mode.next_spawn = 0
- ticker.mode.try_latespawn()
+ message_admins("[key_name(usr)] attempting to force mode autospawn.")
+ ticker.mode.process_autoantag()
diff --git a/code/modules/client/preferences_gear.dm b/code/modules/client/preferences_gear.dm
index 927694ce202..897d95f02f1 100644
--- a/code/modules/client/preferences_gear.dm
+++ b/code/modules/client/preferences_gear.dm
@@ -311,17 +311,18 @@ var/global/list/gear_datums = list()
allowed_roles = list("Security Officer","Head of Security","Warden")
/datum/gear/thugshades
- display_name = "Sunglasses, Fat (Security)"
+ display_name = "Sunglasses, Fat"
path = /obj/item/clothing/glasses/sunglasses/big
cost = 1
slot = slot_glasses
- allowed_roles = list("Security Officer","Head of Security","Warden")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain")
/datum/gear/prescriptionsun
display_name = "sunglasses, presciption"
path = /obj/item/clothing/glasses/sunglasses/prescription
cost = 2
slot = slot_glasses
+ allowed_roles = list("Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain")
// Mask
diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm
index 5f7b07489b9..1032908c835 100644
--- a/code/modules/mob/living/silicon/laws.dm
+++ b/code/modules/mob/living/silicon/laws.dm
@@ -12,7 +12,7 @@
/mob/living/silicon/proc/set_zeroth_law(var/law, var/law_borg)
laws_sanity_check()
laws.set_zeroth_law(law, law_borg)
- log_and_message_admins("has given [src] the zeroth laws: [law]/[law_borg ? law_borg : "N/A"]")
+ log_law("has given [src] the zeroth law: '[law]'[law_borg ? " / '[law_borg]'" : ""]")
/mob/living/silicon/robot/set_zeroth_law(var/law, var/law_borg)
..()
@@ -22,40 +22,40 @@
/mob/living/silicon/proc/add_ion_law(var/law)
laws_sanity_check()
laws.add_ion_law(law)
- log_and_message_admins("has given [src] the ion law: [law]")
+ log_law("has given [src] the ion law: [law]")
/mob/living/silicon/proc/add_inherent_law(var/law)
laws_sanity_check()
laws.add_inherent_law(law)
- log_and_message_admins("has given [src] the inherent law: [law]")
+ log_law("has given [src] the inherent law: [law]")
/mob/living/silicon/proc/add_supplied_law(var/number, var/law)
laws_sanity_check()
laws.add_supplied_law(number, law)
- log_and_message_admins("has given [src] the supplied law: [law]")
+ log_law("has given [src] the supplied law: [law]")
/mob/living/silicon/proc/delete_law(var/datum/ai_law/law)
laws_sanity_check()
laws.delete_law(law)
- log_and_message_admins("has deleted a law belonging to [src]: [law.law]")
+ log_law("has deleted a law belonging to [src]: [law.law]")
/mob/living/silicon/proc/clear_inherent_laws(var/silent = 0)
laws_sanity_check()
laws.clear_inherent_laws()
if(!silent)
- log_and_message_admins("cleared the inherent laws of [src]")
+ log_law("cleared the inherent laws of [src]")
/mob/living/silicon/proc/clear_ion_laws(var/silent = 0)
laws_sanity_check()
laws.clear_ion_laws()
if(!silent)
- log_and_message_admins("cleared the ion laws of [src]")
+ log_law("cleared the ion laws of [src]")
/mob/living/silicon/proc/clear_supplied_laws(var/silent = 0)
laws_sanity_check()
laws.clear_supplied_laws()
if(!silent)
- log_and_message_admins("cleared the supplied laws of [src]")
+ log_law("cleared the supplied laws of [src]")
/mob/living/silicon/proc/statelaws(var/datum/ai_laws/laws)
var/prefix = ""
@@ -103,3 +103,7 @@
/mob/living/silicon/proc/lawsync()
laws_sanity_check()
laws.sort_laws()
+
+/mob/living/silicon/proc/log_law(var/law_message)
+ log_and_message_admins(law_message)
+ lawchanges += "[worldtime2text()] - [usr ? "[key_name(usr)]" : "EVENT"] [law_message]"
diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm
index ece00da5ee7..16a00368a80 100644
--- a/code/modules/mob/living/silicon/say.dm
+++ b/code/modules/mob/living/silicon/say.dm
@@ -68,31 +68,46 @@
if (!message)
return
- var/obj/machinery/hologram/holopad/T = src.holo
- if(T && T.masters[src])//If there is a hologram and its master is the user.
-
- //Human-like, sorta, heard by those who understand humans.
- var/rendered_a
- //Speach distorted, heard by those who do not understand AIs.
- var/message_stars = stars(message)
- var/rendered_b
+ var/obj/machinery/hologram/holopad/H = src.holo
+ if(H && H.masters[src])//If there is a hologram and its master is the user.
+ // AI can hear their own message, this formats it for them.
if(speaking)
- rendered_a = "[name] [speaking.format_message(message, verb)]"
- rendered_b = "[voice_name] [speaking.format_message(message_stars, verb)]"
- src << "Holopad transmitted, [real_name] [speaking.format_message(message, verb)]"//The AI can "hear" its own message.
+ src << "Holopad transmitted, [real_name] [speaking.format_message(message, verb)]"
else
- rendered_a = "[name] [verb], \"[message]\""
- rendered_b = "[voice_name] [verb], \"[message_stars]\""
- src << "Holopad transmitted, [real_name] [verb], \"[message]\""//The AI can "hear" its own message.
+ src << "Holopad transmitted, [real_name] [verb], \"[message]\""
+
+ //This is so pAI's and people inside lockers/boxes,etc can hear the AI Holopad, the alternative being recursion through contents.
+ //This is much faster.
+ var/list/listening = list()
+ var/list/listening_obj = list()
+ var/turf/T = get_turf(H)
+
+ if(T)
+ var/list/hear = hear(7, T)
+ var/list/hearturfs = list()
+
+ for(var/I in hear)
+ if(istype(I, /mob/))
+ var/mob/M = I
+ listening += M
+ hearturfs += M.locs[1]
+ for(var/obj/O in M.contents)
+ listening_obj |= O
+ else if(istype(I, /obj/))
+ var/obj/O = I
+ hearturfs += O.locs[1]
+ listening_obj |= O
+
+
+ for(var/mob/M in player_list)
+ if(M.stat == DEAD && M.client && (M.client.prefs.toggles & CHAT_GHOSTEARS))
+ M.hear_say(message,verb,speaking,null,null, src)
+ continue
+ if(M.loc && M.locs[1] in hearturfs)
+ M.hear_say(message,verb,speaking,null,null, src)
+
- for(var/mob/M in hearers(T.loc))//The location is the object, default distance.
- if(M.say_understands(src))//If they understand AI speak. Humans and the like will be able to.
- M.show_message(rendered_a, 2)
- else//If they do not.
- M.show_message(rendered_b, 2)
- /*Radios "filter out" this conversation channel so we don't need to account for them.
- This is another way of saying that we won't bother dealing with them.*/
else
src << "No holopad connected."
return 0
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index ee5e786db8d..c54ca849e02 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -289,7 +289,7 @@
proc/AttemptLateSpawn(rank,var/spawning_at)
- if (src != usr)
+ if(src != usr)
return 0
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
usr << "\red The round is either not ready, or has already finished..."
@@ -323,7 +323,7 @@
character.loc = C.loc
AnnounceCyborg(character, rank, "has been downloaded to the empty core in \the [character.loc.loc]")
- ticker.mode.latespawn(character)
+ ticker.mode.handle_latejoin(character)
qdel(C)
qdel(src)
@@ -338,7 +338,7 @@
character.buckled.loc = character.loc
character.buckled.set_dir(character.dir)
- ticker.mode.latespawn(character)
+ ticker.mode.handle_latejoin(character)
if(character.mind.assigned_role != "Cyborg")
data_core.manifest_inject(character)
diff --git a/code/modules/reagents/Chemistry-Machinery.dm b/code/modules/reagents/Chemistry-Machinery.dm
index d597e9d8efe..1aebb805e91 100644
--- a/code/modules/reagents/Chemistry-Machinery.dm
+++ b/code/modules/reagents/Chemistry-Machinery.dm
@@ -72,13 +72,8 @@
return
/obj/machinery/chem_master/Topic(href, href_list)
- if(stat & (BROKEN|NOPOWER)) return
- if(usr.stat || usr.restrained()) return
- if(!in_range(src, usr)) return
-
- src.add_fingerprint(usr)
- usr.set_machine(src)
-
+ if(..())
+ return 1
if (href_list["ejectp"])
if(loaded_pill_bottle)
@@ -228,7 +223,7 @@
return src.attack_hand(user)
/obj/machinery/chem_master/attack_hand(mob/user as mob)
- if(stat & BROKEN)
+ if(inoperable())
return
user.set_machine(src)
if(!(user.client in has_sprites))
@@ -337,11 +332,8 @@
/obj/machinery/computer/pandemic/Topic(href, href_list)
- if(stat & (NOPOWER|BROKEN)) return
- if(usr.stat || usr.restrained()) return
- if(!in_range(src, usr)) return
-
- usr.set_machine(src)
+ if(..())
+ return 1
if(!beaker) return
if (href_list["create_vaccine"])
@@ -649,10 +641,12 @@
return 0
/obj/machinery/reagentgrinder/attack_hand(mob/user as mob)
- user.set_machine(src)
interact(user)
/obj/machinery/reagentgrinder/interact(mob/user as mob) // The microwave Menu
+ if(inoperable())
+ return
+ user.set_machine(src)
var/is_chamber_empty = 0
var/is_beaker_ready = 0
var/processing_chamber = ""
@@ -699,8 +693,8 @@
/obj/machinery/reagentgrinder/Topic(href, href_list)
if(..())
- return
- usr.set_machine(src)
+ return 1
+
switch(href_list["action"])
if ("grind")
grind()
@@ -709,7 +703,7 @@
if ("detach")
detach()
src.updateUsrDialog()
- return
+ return 1
/obj/machinery/reagentgrinder/proc/detach()
diff --git a/code/modules/reagents/dispenser/dispenser2.dm b/code/modules/reagents/dispenser/dispenser2.dm
index 8decba10d49..df63f5c8a78 100644
--- a/code/modules/reagents/dispenser/dispenser2.dm
+++ b/code/modules/reagents/dispenser/dispenser2.dm
@@ -113,9 +113,6 @@
return ..()
/obj/machinery/chemical_dispenser/ui_interact(mob/user, ui_key = "main",var/datum/nanoui/ui = null, var/force_open = 1)
- if(stat & (BROKEN|NOPOWER)) return
- if(user.stat || user.restrained()) return
-
// this is the data which will be sent to the ui
var/data[0]
data["amount"] = amount
@@ -148,8 +145,8 @@
ui.open()
/obj/machinery/chemical_dispenser/Topic(href, href_list)
- if(stat & (NOPOWER|BROKEN))
- return 0 // don't update UIs attached to this object
+ if(..())
+ return 1
if(href_list["amount"])
amount = round(text2num(href_list["amount"]), 1) // round to nearest 1
@@ -171,9 +168,7 @@
return 1 // update UIs attached to this object
/obj/machinery/chemical_dispenser/attack_ai(mob/user as mob)
- src.attack_hand(user)
+ ui_interact(user)
/obj/machinery/chemical_dispenser/attack_hand(mob/user as mob)
- if(stat & BROKEN)
- return
ui_interact(user)