"
+ for(var/list/ban in bans)
+ dat += "Server: [sanitize(ban["sourceName"])] "
+ dat += "Type: [sanitize(ban["type"])] "
+ dat += "Banned By: [sanitize(ban["bannedBy"])] "
+ dat += "Reason: [sanitize(ban["reason"])] "
+ dat += "Datetime: [sanitize(ban["bannedOn"])] "
+ var/expiration = ban["expires"]
+ dat += "Expires: [expiration ? "[sanitize(expiration)]" : "Permanent"] "
+ if(ban["type"] == "job")
+ dat += "Jobs: "
+ var/list/jobs = ban["jobs"]
+ dat += sanitize(jobs.Join(", "))
+ dat += " "
+ dat += ""
+
+ dat += " "
+ var/datum/browser/popup = new(usr, "centcomlookup-[ckey]", "
Central Command Galactic Ban Database
", 700, 600)
+ popup.set_content(dat.Join())
+ popup.open(0)
+
else if(href_list["modantagrep"])
if(!check_rights(R_ADMIN))
return
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 115feb5731e..1e93f32d881 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -573,10 +573,10 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
send2adminchat(source,final)
send2otherserver(source,final)
-//
+/// Sends a message to other servers.
/proc/send2otherserver(source,msg,type = "Ahelp",target_servers)
- var/comms_key = CONFIG_GET(string/comms_key)
- if(!comms_key)
+ if(!CONFIG_GET(string/comms_key))
+ debug_world_log("Server cross-comms message not sent for lack of configured key")
return
var/our_id = CONFIG_GET(string/cross_comms_name)
@@ -584,7 +584,6 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
message["message_sender"] = source
message["message"] = msg
message["source"] = "([our_id])"
- message["key"] = comms_key
message += type
var/list/servers = CONFIG_GET(keyed_list/cross_server)
@@ -593,8 +592,23 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
continue
if(target_servers && !(I in target_servers))
continue
- world.Export("[servers[I]]?[list2params(message)]")
+ world.send_cross_comms(I, message)
+/// Sends a message to a given cross comms server by name (by name for security).
+/world/proc/send_cross_comms(server_name, list/message, auth = TRUE)
+ set waitfor = FALSE
+ if (auth)
+ var/comms_key = CONFIG_GET(string/comms_key)
+ if(!comms_key)
+ debug_world_log("Server cross-comms message not sent for lack of configured key")
+ return
+ message["key"] = comms_key
+ var/list/servers = CONFIG_GET(keyed_list/cross_server)
+ var/server_url = servers[server_name]
+ if (!server_url)
+ CRASH("Invalid cross comms config: [server_name]")
+ world.Export("[server_url]?[list2params(message)]")
+
/proc/tgsadminwho()
var/list/message = list("Admins: ")
@@ -668,7 +682,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if(!ai_found && isAI(found))
ai_found = 1
var/is_antag = 0
- if(found.mind && found.mind.special_role)
+ if(is_special_character(found))
is_antag = 1
founds += "Name: [found.name]([found.real_name]) Key: [found.key] Ckey: [found.ckey] [is_antag ? "(Antag)" : null] "
msg += "[original_word](?|F) "
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index a5eacf48a21..2677bdc82e0 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -594,10 +594,10 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
for(var/obj/machinery/field/generator/F in GLOB.machines)
if(F.active == 0)
+ F.set_anchored(TRUE)
F.active = 1
F.state = 2
F.power = 250
- F.anchored = TRUE
F.warming_up = 3
F.start_fields()
F.update_icon()
@@ -889,3 +889,63 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
return
if(alert(usr, "Are you absolutely sure you want to reload the configuration from the default path on the disk, wiping any in-round modificatoins?", "Really reset?", "No", "Yes") == "Yes")
config.admin_reload()
+
+/// A debug verb to check the sources of currently running timers
+/client/proc/check_timer_sources()
+ set category = "Debug"
+ set name = "Check Timer Sources"
+ set desc = "Checks the sources of the running timers"
+ if (!check_rights(R_DEBUG))
+ return
+
+ var/bucket_list_output = generate_timer_source_output(SStimer.bucket_list)
+ var/second_queue = generate_timer_source_output(SStimer.second_queue)
+
+ usr << browse({"
+
bucket_list
+ [bucket_list_output]
+
+
second_queue
+ [second_queue]
+ "}, "window=check_timer_sources;size=700x700")
+
+/proc/generate_timer_source_output(list/datum/timedevent/events)
+ var/list/per_source = list()
+
+ // Collate all events and figure out what sources are creating the most
+ for (var/_event in events)
+ if (!_event)
+ continue
+ var/datum/timedevent/event = _event
+
+ do
+ if (event.source)
+ if (per_source[event.source] == null)
+ per_source[event.source] = 1
+ else
+ per_source[event.source] += 1
+ event = event.next
+ while (event && event != _event)
+
+ // Now, sort them in order
+ var/list/sorted = list()
+ for (var/source in per_source)
+ sorted += list(list("source" = source, "count" = per_source[source]))
+ sorted = sortTim(sorted, .proc/cmp_timer_data)
+
+ // Now that everything is sorted, compile them into an HTML output
+ var/output = "
"
+
+ for (var/_timer_data in sorted)
+ var/list/timer_data = _timer_data
+ output += {"
+
[timer_data["source"]]
+
[timer_data["count"]]
+
"}
+
+ output += "
"
+
+ return output
+
+/proc/cmp_timer_data(list/a, list/b)
+ return b["count"] - a["count"]
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index b074c2c743b..05953a5b959 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -1069,6 +1069,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
ADMIN_PUNISHMENT_NUGGET,
ADMIN_PUNISHMENT_CRACK,
ADMIN_PUNISHMENT_BLEED,
+ ADMIN_PUNISHMENT_PERFORATE,
ADMIN_PUNISHMENT_SCARIFY,
ADMIN_PUNISHMENT_SHOES
)
@@ -1159,41 +1160,112 @@ Traitors and the like can also be revived with the previous role mostly intact.
to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
return
var/mob/living/carbon/C = target
- for(var/obj/item/bodypart/squish_part in C.bodyparts)
- var/type_wound = pick(list(/datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/moderate))
+ for(var/i in C.bodyparts)
+ var/obj/item/bodypart/squish_part = i
+ var/type_wound = pick(list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate))
squish_part.force_wound_upwards(type_wound, smited=TRUE)
if(ADMIN_PUNISHMENT_BLEED)
if(!iscarbon(target))
to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
return
var/mob/living/carbon/C = target
- for(var/obj/item/bodypart/slice_part in C.bodyparts)
- var/type_wound = pick(list(/datum/wound/brute/cut/severe, /datum/wound/brute/cut/moderate))
+ for(var/i in C.bodyparts)
+ var/obj/item/bodypart/slice_part = i
+ var/type_wound = pick(list(/datum/wound/slash/severe, /datum/wound/slash/moderate))
slice_part.force_wound_upwards(type_wound, smited=TRUE)
- type_wound = pick(list(/datum/wound/brute/cut/critical, /datum/wound/brute/cut/severe, /datum/wound/brute/cut/moderate))
+ type_wound = pick(list(/datum/wound/slash/critical, /datum/wound/slash/severe, /datum/wound/slash/moderate))
slice_part.force_wound_upwards(type_wound, smited=TRUE)
- type_wound = pick(list(/datum/wound/brute/cut/critical, /datum/wound/brute/cut/severe))
+ type_wound = pick(list(/datum/wound/slash/critical, /datum/wound/slash/severe))
slice_part.force_wound_upwards(type_wound, smited=TRUE)
+ if(ADMIN_PUNISHMENT_PERFORATE)
+ if(!iscarbon(target))
+ to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
+ return
+
+ var/list/how_fucked_is_this_dude = list("A little", "A lot", "So fucking much", "FUCK THIS DUDE")
+ var/hatred = input("How much do you hate this guy?") in how_fucked_is_this_dude
+ var/repetitions
+ var/shots_per_limb_per_rep = 2
+ var/damage
+ switch(hatred)
+ if("A little")
+ repetitions = 1
+ damage = 5
+ if("A lot")
+ repetitions = 2
+ damage = 8
+ if("So fucking much")
+ repetitions = 3
+ damage = 10
+ if("FUCK THIS DUDE")
+ repetitions = 4
+ damage = 10
+
+ var/mob/living/carbon/dude = target
+ var/list/open_adj_turfs = get_adjacent_open_turfs(dude)
+ var/list/wound_bonuses = list(15, 70, 110, 250)
+
+ var/delay_per_shot = 1
+ var/delay_counter = 1
+
+ dude.Immobilize(5 SECONDS)
+ for(var/wound_bonus_rep in 1 to repetitions)
+ for(var/i in dude.bodyparts)
+ var/obj/item/bodypart/slice_part = i
+ var/shots_this_limb = 0
+ for(var/t in shuffle(open_adj_turfs))
+ var/turf/iter_turf = t
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/firing_squad, dude, iter_turf, slice_part.body_zone, wound_bonuses[wound_bonus_rep], damage), delay_counter)
+ delay_counter += delay_per_shot
+ shots_this_limb++
+ if(shots_this_limb > shots_per_limb_per_rep)
+ break
+
if(ADMIN_PUNISHMENT_SCARIFY)
if(!iscarbon(target))
to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
return
- var/mob/living/carbon/C = target
- C.generate_fake_scars(rand(1, 4))
- to_chat(C, "You feel your body grow jaded and torn...")
+ var/mob/living/carbon/dude = target
+ dude.generate_fake_scars(rand(1, 4))
+ to_chat(dude, "You feel your body grow jaded and torn...")
if(ADMIN_PUNISHMENT_SHOES)
if(!iscarbon(target))
to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
return
- var/mob/living/carbon/C = target
- var/obj/item/clothing/shoes/sick_kicks = C.shoes
+ var/mob/living/carbon/dude = target
+ var/obj/item/clothing/shoes/sick_kicks = dude.shoes
if(!sick_kicks?.can_be_tied)
- to_chat(usr,"[C] does not have knottable shoes!", confidential = TRUE)
+ to_chat(usr,"[dude] does not have knottable shoes!", confidential = TRUE)
return
sick_kicks.adjust_laces(SHOES_KNOTTED)
punish_log(target, punishment)
+/**
+ * firing_squad is a proc for the :B:erforate smite to shoot each individual bullet at them, so that we can add actual delays without sleep() nonsense
+ *
+ * Hilariously, if you drag someone away mid smite, the bullets will still chase after them from the original spot, possibly hitting other people. Too funny to fix imo
+ *
+ * Arguments:
+ * * target- guy we're shooting obviously
+ * * source_turf- where the bullet begins, preferably on a turf next to the target
+ * * body_zone- which bodypart we're aiming for, if there is one there
+ * * wound_bonus- the wounding power we're assigning to the bullet, since we don't care about the base one
+ * * damage- the damage we're assigning to the bullet, since we don't care about the base one
+ */
+/proc/firing_squad(mob/living/carbon/target, turf/source_turf, body_zone, wound_bonus, damage)
+ if(!target.get_bodypart(body_zone))
+ return
+ playsound(target, 'sound/weapons/gun/revolver/shot.ogg', 100)
+ var/obj/projectile/bullet/smite/divine_wrath = new(source_turf)
+ divine_wrath.damage = damage
+ divine_wrath.wound_bonus = wound_bonus
+ divine_wrath.original = target
+ divine_wrath.def_zone = body_zone
+ divine_wrath.spread = 0
+ divine_wrath.preparePixelProjectile(target, source_turf)
+ divine_wrath.fire()
+
/client/proc/punish_log(whom, punishment)
var/msg = "[key_name_admin(usr)] punished [key_name_admin(whom)] with [punishment]."
message_admins(msg)
@@ -1225,7 +1297,9 @@ Traitors and the like can also be revived with the previous role mostly intact.
var/list/msg = list()
msg += "Playtime ReportPlaytime:
"
- for(var/client/C in GLOB.clients)
+ var/list/clients_list_copy = GLOB.clients.Copy()
+ sortList(clients_list_copy)
+ for(var/client/C in clients_list_copy)
msg += "
"
dat = dat.Join()
- usr << browse(dat, "window=ref_view") //Done this way rather than tgui to facilitate porting to other codebases or even byond games
+ var/datum/browser/popup = new(usr, "ref_view", "
References of \ref[D]
")
+ popup.set_content(dat)
+ popup.open(FALSE)
+
+
+/datum/admins/proc/view_del_failures()
+ set category = "Debug"
+ set name = "View Deletion Failures"
+
+ if(!check_rights(R_DEBUG))
+ return
+
+ var/list/dat = list("
")
+ for(var/t in GLOB.deletion_failures)
+ if(isnull(t))
+ dat += "
")
+ popup.set_content(dat)
+ popup.open(FALSE)
+
+
+/datum/proc/find_references()
+ testing("Beginning search for references to a [type].")
+ var/list/backrefs = get_back_references(src)
+ for(var/ref in backrefs)
+ if(isnull(ref))
+ log_world("## TESTING: Datum reference found, but gone now.")
+ continue
+ if(islist(ref))
+ log_world("## TESTING: Found [type] \ref[src] in list.")
+ continue
+ var/datum/datum_ref = ref
+ if(!istype(datum_ref))
+ log_world("## TESTING: Found [type] \ref[src] in unknown type reference: [datum_ref].")
+ return
+ log_world("## TESTING: Found [type] \ref[src] in [datum_ref.type][datum_ref.gc_destroyed ? " (destroyed)" : ""]")
+ message_admins("Found [type] \ref[src] [ADMIN_VV(src)] in [datum_ref.type][datum_ref.gc_destroyed ? " (destroyed)" : ""] [ADMIN_VV(datum_ref)]")
+ testing("Completed search for references to a [type].")
+
+#endif
+
+#ifdef LEGACY_REFERENCE_TRACKING
+
+/datum/verb/legacy_find_refs()
+ set category = "Debug"
+ set name = "Find References"
+ set src in world
+
+ find_references(FALSE)
+
+
+/datum/proc/find_references_legacy(skip_alert)
+ running_find_references = type
+ if(usr?.client)
+ if(usr.client.running_find_references)
+ testing("CANCELLED search for references to a [usr.client.running_find_references].")
+ usr.client.running_find_references = null
+ running_find_references = null
+ //restart the garbage collector
+ SSgarbage.can_fire = TRUE
+ SSgarbage.next_fire = world.time + world.tick_lag
+ return
+
+ if(!skip_alert && alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes")
+ running_find_references = null
+ return
+
+ //this keeps the garbage collector from failing to collect objects being searched for in here
+ SSgarbage.can_fire = FALSE
+
+ if(usr?.client)
+ usr.client.running_find_references = type
+
+ testing("Beginning search for references to a [type].")
+ last_find_references = world.time
+
+ DoSearchVar(GLOB) //globals
+ for(var/datum/thing in world) //atoms (don't beleive its lies)
+ DoSearchVar(thing, "World -> [thing]")
+
+ for(var/datum/thing) //datums
+ DoSearchVar(thing, "World -> [thing]")
+
+ for(var/client/thing) //clients
+ DoSearchVar(thing, "World -> [thing]")
+
+ testing("Completed search for references to a [type].")
+ if(usr?.client)
+ usr.client.running_find_references = null
+ running_find_references = null
+
+ //restart the garbage collector
+ SSgarbage.can_fire = TRUE
+ SSgarbage.next_fire = world.time + world.tick_lag
+
+
+/datum/verb/qdel_then_find_references()
+ set category = "Debug"
+ set name = "qdel() then Find References"
+ set src in world
+
+ qdel(src, TRUE) //force a qdel
+ if(!running_find_references)
+ find_references(TRUE)
+
+
+/datum/verb/qdel_then_if_fail_find_references()
+ set category = "Debug"
+ set name = "qdel() then Find References if GC failure"
+ set src in world
+
+ qdel_and_find_ref_if_fail(src, TRUE)
+
+
+/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64)
+ if(usr?.client && !usr.client.running_find_references)
+ return
+
+ if(!recursive_limit)
+ return
+
+ if(istype(potential_container, /datum))
+ var/datum/datum_container = potential_container
+ if(datum_container.last_find_references == last_find_references)
+ return
+
+ datum_container.last_find_references = last_find_references
+ var/list/vars_list = datum_container.vars
+
+ for(var/varname in vars_list)
+ if (varname == "vars")
+ continue
+ var/variable = vars_list[varname]
+
+ if(variable == src)
+ testing("Found [type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]")
+
+ else if(islist(variable))
+ DoSearchVar(variable, "[container_name] -> list", recursive_limit - 1)
+
+ else if(islist(potential_container))
+ var/normal = IS_NORMAL_LIST(potential_container)
+ for(var/element_in_list in potential_container)
+ if(element_in_list == src)
+ testing("Found [type] \ref[src] in list [container_name].")
+
+ else if(element_in_list && !isnum(element_in_list) && normal && potential_container[element_in_list] == src)
+ testing("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
+
+ else if(islist(element_in_list))
+ DoSearchVar(element_in_list, "[container_name] -> list", recursive_limit - 1)
+
+ #ifndef FIND_REF_NO_CHECK_TICK
+ CHECK_TICK
+ #endif
+
+
+/proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE)
+ SSgarbage.reference_find_on_fail[REF(thing_to_del)] = TRUE
+ qdel(thing_to_del, force)
+
+#endif
diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm
index 6e3f2bd1a01..4949139c658 100644
--- a/code/modules/admin/view_variables/topic_basic.dm
+++ b/code/modules/admin/view_variables/topic_basic.dm
@@ -45,6 +45,18 @@
usr.client.admin_delete(target)
if (isturf(src)) // show the turf that took its place
usr.client.debug_variables(src)
+ return
+
+ #ifdef REFERENCE_TRACKING
+ if(href_list[VV_HK_VIEW_REFERENCES])
+ var/datum/D = locate(href_list[VV_HK_TARGET])
+ if(!D)
+ to_chat(usr, "Unable to locate item.")
+ return
+ usr.client.holder.view_refs(target)
+ return
+ #endif
+
if(href_list[VV_HK_MARK])
usr.client.mark_datum(target)
if(href_list[VV_HK_ADDCOMPONENT])
@@ -77,5 +89,4 @@
message_admins("[key_name_admin(usr)] has added [result] [datumname] to [key_name_admin(src)].")
if(href_list[VV_HK_CALLPROC])
usr.client.callproc_datum(target)
- if(href_list[VV_HK_VIEW_REFERENCES])
- usr.client.view_refs(target)
+
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 7bac12db69f..f8c054b9941 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -266,7 +266,7 @@ GLOBAL_LIST_EMPTY(antagonists)
show_name_in_check_antagonists = TRUE //They're all different
var/datum/team/custom_team
-datum/antagonist/custom/create_team(datum/team/team)
+/datum/antagonist/custom/create_team(datum/team/team)
custom_team = team
/datum/antagonist/custom/get_team()
diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm
index 03e9d1d4675..f35b2f4fc84 100644
--- a/code/modules/antagonists/_common/antag_spawner.dm
+++ b/code/modules/antagonists/_common/antag_spawner.dm
@@ -96,12 +96,18 @@
///////////BORGS AND OPERATIVES
+/**
+ * Device to request reinforcments from ghost pop
+ */
/obj/item/antag_spawner/nuke_ops
name = "syndicate operative teleporter"
desc = "A single-use teleporter designed to quickly reinforce operatives in the field."
icon = 'icons/obj/device.dmi'
icon_state = "locator"
var/borg_to_spawn
+ var/special_role_name = "Nuclear Operative" ///The name of the special role given to the recruit
+ var/datum/outfit/syndicate/outfit = /datum/outfit/syndicate/no_crystals ///The applied outfit
+ var/datum/antagonist/nukeop/antag_datum = /datum/antagonist/nukeop ///The antag datam applied
/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user)
if(used)
@@ -112,7 +118,6 @@
return FALSE
return TRUE
-
/obj/item/antag_spawner/nuke_ops/attack_self(mob/user)
if(!(check_usability(user)))
return
@@ -134,35 +139,23 @@
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
+ var/datum/mind/op_mind = M.mind
- var/datum/antagonist/nukeop/new_op = new()
- new_op.send_to_spawnpoint = FALSE
- new_op.nukeop_outfit = /datum/outfit/syndicate/no_crystals
+ antag_datum = new()
+ antag_datum.send_to_spawnpoint = FALSE
+ antag_datum.nukeop_outfit = outfit
- var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE)
- if(creator_op)
- M.mind.add_antag_datum(new_op,creator_op.nuke_team)
- M.mind.special_role = "Nuclear Operative"
+ var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop, TRUE)
+ op_mind.add_antag_datum(antag_datum, creator_op ? creator_op.get_team() : null)
+ op_mind.special_role = special_role_name
//////CLOWN OP
/obj/item/antag_spawner/nuke_ops/clown
name = "clown operative teleporter"
desc = "A single-use teleporter designed to quickly reinforce clown operatives in the field."
-
-/obj/item/antag_spawner/nuke_ops/clown/spawn_antag(client/C, turf/T, kind, datum/mind/user)
- var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
- C.prefs.copy_to(M)
- M.key = C.key
-
- var/datum/antagonist/nukeop/clownop/new_op = new /datum/antagonist/nukeop/clownop()
- new_op.send_to_spawnpoint = FALSE
- new_op.nukeop_outfit = /datum/outfit/syndicate/clownop/no_crystals
-
- var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop/clownop,TRUE)
- if(creator_op)
- M.mind.add_antag_datum(new_op, creator_op.nuke_team)
- M.mind.special_role = "Clown Operative"
-
+ special_role_name = "Clown Operative"
+ outfit = /datum/outfit/syndicate/clownop/no_crystals
+ antag_datum = /datum/antagonist/nukeop/clownop
//////SYNDICATE BORG
/obj/item/antag_spawner/nuke_ops/borg_tele
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index 93a8dec8d6c..290660e08d1 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -161,7 +161,7 @@
throw_speed = 0
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
wound_bonus = -60
bare_wound_bonus = 20
var/can_drop = FALSE
diff --git a/code/modules/antagonists/changeling/powers/regenerate.dm b/code/modules/antagonists/changeling/powers/regenerate.dm
index d65d1181bed..a060cda7f8e 100644
--- a/code/modules/antagonists/changeling/powers/regenerate.dm
+++ b/code/modules/antagonists/changeling/powers/regenerate.dm
@@ -35,8 +35,8 @@
B.Insert(C)
C.regenerate_organs()
for(var/i in C.all_wounds)
- var/datum/wound/W = i
- W.remove_wound()
+ var/datum/wound/iter_wound = i
+ iter_wound.remove_wound()
if(ishuman(user))
var/mob/living/carbon/human/H = user
H.restore_blood()
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index af7a7493e28..7919fd125bd 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -39,7 +39,7 @@
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
flags_1 = CONDUCT_1
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
w_class = WEIGHT_CLASS_BULKY
force = 30 // whoever balanced this got beat in the head by a bible too many times good lord
throwforce = 10
@@ -91,7 +91,7 @@
armour_penetration = 45
throw_speed = 1
throw_range = 3
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
light_color = "#ff0000"
attack_verb = list("cleaved", "slashed", "tore", "lacerated", "hacked", "ripped", "diced", "carved")
icon_state = "cultbastard"
@@ -627,7 +627,7 @@
armour_penetration = 30
block_chance = 30
attack_verb = list("attacked", "impaled", "stabbed", "tore", "lacerated", "gored")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
hitsound = 'sound/weapons/bladeslice.ogg'
var/datum/action/innate/cult/spear/spear_act
var/wielded = FALSE // track wielded status on item
diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm
index 2f51431f6dd..596d996ee07 100644
--- a/code/modules/antagonists/cult/cult_structures.dm
+++ b/code/modules/antagonists/cult/cult_structures.dm
@@ -54,14 +54,19 @@
else
..()
+/obj/structure/destructible/cult/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return
+ update_icon()
+
+/obj/structure/destructible/cult/update_icon_state()
+ icon_state = "[initial(icon_state)][anchored ? null : "_off"]"
+
/obj/structure/destructible/cult/attackby(obj/I, mob/user, params)
if(istype(I, /obj/item/melee/cultblade/dagger) && iscultist(user))
- anchored = !anchored
+ set_anchored(!anchored)
to_chat(user, "You [anchored ? "":"un"]secure \the [src] [anchored ? "to":"from"] the floor.")
- if(!anchored)
- icon_state = "[initial(icon_state)]_off"
- else
- icon_state = initial(icon_state)
else
return ..()
diff --git a/code/modules/antagonists/devil/true_devil/_true_devil.dm b/code/modules/antagonists/devil/true_devil/_true_devil.dm
index d2383d7d7e0..1a725daba5b 100644
--- a/code/modules/antagonists/devil/true_devil/_true_devil.dm
+++ b/code/modules/antagonists/devil/true_devil/_true_devil.dm
@@ -123,7 +123,7 @@
var/weakness = check_weakness(I, user)
apply_damage(I.force * weakness, I.damtype, def_zone)
var/message_verb = ""
- if(I.attack_verb && I.attack_verb.len)
+ if(I.attack_verb && length(I.attack_verb))
message_verb = "[pick(I.attack_verb)]"
else if(I.force)
message_verb = "attacked"
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_book.dm b/code/modules/antagonists/eldritch_cult/eldritch_book.dm
index 575670d9ec7..f3135fe6599 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_book.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_book.dm
@@ -3,6 +3,7 @@
desc = "Book describing the secrets of the veil."
icon = 'icons/obj/eldritch.dmi'
icon_state = "book"
+ worn_icon_state = "book"
w_class = WEIGHT_CLASS_SMALL
///Last person that touched this
var/mob/living/last_user
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
index f450d9d6a90..fba5de445a1 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_items.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
@@ -41,7 +41,7 @@
inhand_x_dimension = 64
inhand_y_dimension = 64
flags_1 = CONDUCT_1
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
w_class = WEIGHT_CLASS_NORMAL
force = 17
throwforce = 10
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
index bac3767ce42..20224b39e65 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
@@ -140,14 +140,15 @@
if(iscarbon(target))
var/mob/living/carbon/C1 = target
for(var/obj/item/bodypart/bodypart in C2.bodyparts)
- for(var/datum/wound/wound in bodypart.wounds)
+ for(var/i in bodypart.wounds)
+ var/datum/wound/iter_wound = i
if(prob(50))
continue
var/obj/item/bodypart/target_bodypart = locate(bodypart.type) in C1.bodyparts
if(!target_bodypart)
continue
- wound.remove_wound()
- wound.apply_wound(target_bodypart)
+ iter_wound.remove_wound()
+ iter_wound.apply_wound(target_bodypart)
C1.blood_volume -= 20
if(C2.blood_volume < BLOOD_VOLUME_MAXIMUM) //we dont want to explode after all
@@ -241,7 +242,7 @@
target.visible_message("[target]'s veins are shredded from within as an unholy blaze erupts from their blood!", \
"Your veins burst from within and unholy flame erupts from your blood!")
var/obj/item/bodypart/bodypart = pick(target.bodyparts)
- var/datum/wound/brute/cut/critical/crit_wound = new
+ var/datum/wound/slash/critical/crit_wound = new
crit_wound.apply_wound(bodypart)
target.adjustFireLoss(20)
new /obj/effect/temp_visual/cleave(target.drop_location())
@@ -406,6 +407,8 @@
for(var/turf/T in spiral_range_turfs(_range,centre))
new /obj/effect/hotspot(T)
T.hotspot_expose(700,50,1)
+ for(var/mob/living/livies in T.contents - centre)
+ livies.adjustFireLoss(10)
_range++
sleep(3)
@@ -453,6 +456,8 @@
for(var/turf/T in range(1,current_user))
new /obj/effect/hotspot(T)
T.hotspot_expose(700,50,1)
+ for(var/mob/living/livies in T.contents - current_user)
+ livies.adjustFireLoss(5)
/obj/effect/proc_holder/spell/targeted/worm_contract
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
index 3d579853ba9..597054c4adc 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
@@ -86,7 +86,7 @@
if(iscarbon(target))
var/mob/living/carbon/carbon_target = target
var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts)
- var/datum/wound/brute/cut/severe/crit_wound = new
+ var/datum/wound/slash/severe/crit_wound = new
crit_wound.apply_wound(bodypart)
if(QDELETED(human_target) || human_target.stat != DEAD)
@@ -157,7 +157,7 @@
if(iscarbon(target))
var/mob/living/carbon/carbon_target = target
var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts)
- var/datum/wound/brute/cut/severe/crit_wound = new
+ var/datum/wound/slash/severe/crit_wound = new
crit_wound.apply_wound(bodypart)
/datum/eldritch_knowledge/summon/raw_prophet
diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
index afe1f40f062..64dfc71b740 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -393,7 +393,7 @@
if(isinspace() && !anchored)
to_chat(usr, "There is nothing to anchor to!")
else
- anchored = !anchored
+ set_anchored(!anchored)
/obj/machinery/nuclearbomb/proc/set_safety()
safety = !safety
@@ -662,6 +662,15 @@ This is here to make the tiles around the station mininuke change when it's arme
if(isobserver(user) || HAS_TRAIT(user.mind, TRAIT_DISK_VERIFIER))
. += "The serial numbers on [src] are incorrect."
+/*
+ * You can't accidentally eat the nuke disk, bro
+ */
+/obj/item/disk/nuclear/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ M.visible_message("[M] looks like [M.p_theyve()] just bitten into something important.", \
+ "Wait, is this the nuke disk?")
+
+ return discover_after
+
/obj/item/disk/nuclear/attackby(obj/item/I, mob/living/user, params)
if(istype(I, /obj/item/claymore/highlander) && !fake)
var/obj/item/claymore/highlander/H = I
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index 35018d28742..d24c9c7d21c 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -49,7 +49,6 @@
move_resist = MOVE_FORCE_OVERPOWERING
mob_size = MOB_SIZE_TINY
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
- flags_1 = RAD_NO_CONTAMINATE_1
speed = 1
unique_name = TRUE
hud_possible = list(ANTAG_HUD)
@@ -73,6 +72,7 @@
/mob/living/simple_animal/revenant/Initialize(mapload)
. = ..()
+ flags_1 |= RAD_NO_CONTAMINATE_1
ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
ADD_TRAIT(src, TRAIT_SIXTHSENSE, INNATE_TRAIT)
AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision/revenant(null))
diff --git a/code/modules/antagonists/slaughter/slaughter.dm b/code/modules/antagonists/slaughter/slaughter.dm
index 13fa3793cde..b140c343c50 100644
--- a/code/modules/antagonists/slaughter/slaughter.dm
+++ b/code/modules/antagonists/slaughter/slaughter.dm
@@ -38,7 +38,7 @@
melee_damage_upper = 15
wound_bonus = -10
bare_wound_bonus = 0
- sharpness = TRUE
+ sharpness = SHARP_EDGED
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
var/playstyle_string = "You are a slaughter demon, a terrible creature from another realm. You have a single desire: To kill. \
diff --git a/code/modules/antagonists/swarmer/swarmer.dm b/code/modules/antagonists/swarmer/swarmer.dm
index 9aae6a2e7b1..57de089d449 100644
--- a/code/modules/antagonists/swarmer/swarmer.dm
+++ b/code/modules/antagonists/swarmer/swarmer.dm
@@ -1,675 +1,38 @@
-////Deactivated swarmer shell////
-/obj/item/deactivated_swarmer
- name = "deactivated swarmer"
- desc = "A shell of swarmer that was completely powered down. It can no longer activate itself."
- icon = 'icons/mob/swarmer.dmi'
- icon_state = "swarmer_unactivated"
- custom_materials = list(/datum/material/iron=10000, /datum/material/glass=4000)
+/datum/team/swarmer
+ name = "Swarmers"
-/obj/effect/mob_spawn/swarmer
- name = "unactivated swarmer"
- desc = "A currently unactivated swarmer. Swarmers can self activate at any time, so it would be wise to immediately dispose of this."
- icon = 'icons/mob/swarmer.dmi'
- icon_state = "swarmer_unactivated"
- density = FALSE
- anchored = FALSE
+//Simply lists them.
+/datum/team/swarmer/roundend_report()
+ var/list/parts = list()
+ parts += "The [name] were:"
+ parts += printplayerlist(members)
+ return "
[parts.Join(" ")]
"
- mob_type = /mob/living/simple_animal/hostile/swarmer
- mob_name = "a swarmer"
- death = FALSE
- roundstart = FALSE
- short_desc = "You are a swarmer, a weapon of a long dead civilization."
- flavour_text = {"
- You are a swarmer, a weapon of a long dead civilization. Until further orders from your original masters are received, you must continue to consume and replicate.
- Clicking on any object will try to consume it, either deconstructing it into its components, destroying it, or integrating any materials it has into you if successful.
- Ctrl-Clicking on a mob will attempt to remove it from the area and place it in a safe environment for storage.
- Objectives:
- 1. Consume resources and replicate until there are no more resources left.
- 2. Ensure that this location is fit for invasion at a later date; do not perform actions that would render it dangerous or inhospitable.
- 3. Biological resources will be harvested at a later date; do not harm them.
- "}
-
-/obj/effect/mob_spawn/swarmer/Initialize()
- . = ..()
- var/area/A = get_area(src)
- if(A)
- notify_ghosts("A swarmer shell has been created in [A.name].", 'sound/effects/bin_close.ogg', source = src, action = NOTIFY_ATTACK, flashwindow = FALSE)
-
-/obj/effect/mob_spawn/swarmer/attack_hand(mob/living/user)
- . = ..()
- if(.)
- return
- to_chat(user, "Picking up the swarmer may cause it to activate. You should be careful about this.")
-
-/obj/effect/mob_spawn/swarmer/attackby(obj/item/W, mob/user, params)
- if(W.tool_behaviour == TOOL_SCREWDRIVER && user.a_intent != INTENT_HARM)
- user.visible_message("[usr.name] deactivates [src].",
- "After some fiddling, you find a way to disable [src]'s power source.",
- "You hear clicking.")
- new /obj/item/deactivated_swarmer(get_turf(src))
- qdel(src)
- else
- ..()
-
-////The Mob itself////
-
-/mob/living/simple_animal/hostile/swarmer
+/datum/antagonist/swarmer
name = "Swarmer"
- unique_name = 1
- icon = 'icons/mob/swarmer.dmi'
- desc = "Robotic constructs of unknown design, swarmers seek only to consume materials and replicate themselves indefinitely."
- speak_emote = list("tones")
- initial_language_holder = /datum/language_holder/swarmer
- bubble_icon = "swarmer"
- mob_biotypes = MOB_ROBOTIC
- health = 40
- maxHealth = 40
- status_flags = CANPUSH
- icon_state = "swarmer"
- icon_living = "swarmer"
- icon_dead = "swarmer_unactivated"
- icon_gib = null
- wander = 0
- harm_intent_damage = 5
- minbodytemp = 0
- maxbodytemp = 500
- atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
- unsuitable_atmos_damage = 0
- melee_damage_lower = 15
- melee_damage_upper = 15
- melee_damage_type = STAMINA
- damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
- hud_possible = list(ANTAG_HUD, DIAG_STAT_HUD, DIAG_HUD)
- obj_damage = 0
- environment_smash = ENVIRONMENT_SMASH_NONE
- attack_verb_continuous = "shocks"
- attack_verb_simple = "shock"
- attack_sound = 'sound/effects/empulse.ogg'
- friendly_verb_continuous = "pinches"
- friendly_verb_simple = "pinch"
- speed = 0
- faction = list("swarmer")
- AIStatus = AI_OFF
- pass_flags = PASSTABLE
- mob_size = MOB_SIZE_TINY
- ventcrawler = VENTCRAWLER_ALWAYS
- ranged = 1
- projectiletype = /obj/projectile/beam/disabler
- ranged_cooldown_time = 20
- projectilesound = 'sound/weapons/taser2.ogg'
- loot = list(/obj/effect/decal/cleanable/robot_debris, /obj/item/stack/ore/bluespace_crystal)
- del_on_death = 1
- deathmessage = "explodes with a sharp pop!"
- light_color = LIGHT_COLOR_CYAN
- hud_type = /datum/hud/swarmer
- speech_span = SPAN_ROBOT
- var/resources = 0 //Resource points, generated by consuming metal/glass
- var/max_resources = 100
+ job_rank = ROLE_ALIEN
+ show_in_antagpanel = FALSE
+ prevent_roundtype_conversion = FALSE
+ var/datum/team/swarmer/swarmer_team
-/mob/living/simple_animal/hostile/swarmer/Initialize()
- . = ..()
- verbs -= /mob/living/verb/pulled
- for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
- diag_hud.add_to_hud(src)
+/datum/antagonist/swarmer/create_team(datum/team/swarmer/new_team)
+ if(!new_team)
+ for(var/datum/antagonist/swarmer/swarmerantag in GLOB.antagonists)
+ if(!swarmerantag.owner || !swarmerantag.swarmer_team)
+ continue
+ swarmer_team = swarmerantag.swarmer_team
+ return
+ swarmer_team = new
+ else
+ if(!istype(new_team))
+ CRASH("Wrong swarmer team type provided to create_team")
+ swarmer_team = new_team
-/mob/living/simple_animal/hostile/swarmer/med_hud_set_health()
- var/image/holder = hud_list[DIAG_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - world.icon_size
- holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]"
+/datum/antagonist/swarmer/get_team()
+ return swarmer_team
-/mob/living/simple_animal/hostile/swarmer/med_hud_set_status()
- var/image/holder = hud_list[DIAG_STAT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - world.icon_size
- holder.icon_state = "hudstat"
-
-/mob/living/simple_animal/hostile/swarmer/Stat()
+//SWARMER
+/mob/living/simple_animal/hostile/swarmer/mind_initialize()
..()
- if(statpanel("Status"))
- stat("Resources:",resources)
-
-/mob/living/simple_animal/hostile/swarmer/emp_act()
- . = ..()
- if(. & EMP_PROTECT_SELF)
- return
- if(health > 1)
- adjustHealth(health-1)
- else
- death()
-
-/mob/living/simple_animal/hostile/swarmer/CanAllowThrough(atom/movable/O)
- . = ..()
- if(istype(O, /obj/projectile/beam/disabler))//Allows for swarmers to fight as a group without wasting their shots hitting each other
- return TRUE
- if(isswarmer(O))
- return TRUE
-
-////CTRL CLICK FOR SWARMERS AND SWARMER_ACT()'S////
-/mob/living/simple_animal/hostile/swarmer/AttackingTarget()
- if(!isliving(target))
- return target.swarmer_act(src)
- else
- return ..()
-
-/mob/living/simple_animal/hostile/swarmer/CtrlClickOn(atom/A)
- face_atom(A)
- if(!isturf(loc))
- return
- if(next_move > world.time)
- return
- if(!A.Adjacent(src))
- return
- A.swarmer_act(src)
-
-/atom/proc/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DisIntegrate(src)
- return TRUE //return TRUE/FALSE whether or not an AI swarmer should try this swarmer_act() again, NOT whether it succeeded.
-
-/obj/effect/mob_spawn/swarmer/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.Integrate(src)
- return FALSE //would logically be TRUE, but we don't want AI swarmers eating player spawn chances.
-
-/obj/effect/mob_spawn/swarmer/IntegrateAmount()
- return 50
-
-/turf/closed/indestructible/swarmer_act()
- return FALSE
-
-/obj/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- if(resistance_flags & INDESTRUCTIBLE)
- return FALSE
- for(var/mob/living/L in contents)
- if(!issilicon(L) && !isbrain(L))
- to_chat(S, "An organism has been detected inside this object. Aborting.")
- return FALSE
- return ..()
-
-/obj/item/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- return S.Integrate(src)
-
-/atom/movable/proc/IntegrateAmount()
- return 0
-
-/obj/item/IntegrateAmount() //returns the amount of resources gained when eating this item
- if(custom_materials)
- if(custom_materials[SSmaterials.GetMaterialRef(/datum/material/iron)] || custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)])
- return 1
- return ..()
-
-/obj/item/gun/swarmer_act()//Stops you from eating the entire armory
- return FALSE
-
-/turf/open/swarmer_act()//ex_act() on turf calls it on its contents, this is to prevent attacking mobs by DisIntegrate()'ing the floor
- return FALSE
-
-/obj/structure/lattice/catwalk/swarmer_catwalk/swarmer_act()
- return FALSE
-
-/obj/structure/swarmer/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- if(S.AIStatus == AI_ON)
- return FALSE
- else
- return ..()
-
-/obj/effect/swarmer_act()
- return FALSE
-
-/obj/effect/decal/cleanable/robot_debris/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DisIntegrate(src)
- qdel(src)
- return TRUE
-
-/obj/structure/flora/swarmer_act()
- return FALSE
-
-/turf/open/lava/swarmer_act()
- if(!is_safe())
- new /obj/structure/lattice/catwalk/swarmer_catwalk(src)
- return FALSE
-
-/obj/machinery/atmospherics/swarmer_act()
- return FALSE
-
-/obj/structure/disposalpipe/swarmer_act()
- return FALSE
-
-/obj/machinery/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DismantleMachine(src)
- return TRUE
-
-/obj/machinery/light/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DisIntegrate(src)
- return TRUE
-
-/obj/machinery/door/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- var/isonshuttle = istype(get_area(src), /area/shuttle)
- for(var/turf/T in range(1, src))
- var/area/A = get_area(T)
- if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
- to_chat(S, "Destroying this object has the potential to cause a hull breach. Aborting.")
- S.target = null
- return FALSE
- else if(istype(A, /area/engine/supermatter))
- to_chat(S, "Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.")
- S.target = null
- return FALSE
- S.DisIntegrate(src)
- return TRUE
-
-/obj/machinery/camera/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DisIntegrate(src)
- if(!QDELETED(S)) //If it got blown up no need to turn it off.
- toggle_cam(S, 0)
- return TRUE
-
-/obj/machinery/particle_accelerator/control_box/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DisIntegrate(src)
- return TRUE
-
-/obj/machinery/field/generator/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DisIntegrate(src)
- return TRUE
-
-/obj/machinery/gravity_generator/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DisIntegrate(src)
- return TRUE
-
-/obj/machinery/vending/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)//It's more visually interesting than dismantling the machine
- S.DisIntegrate(src)
- return TRUE
-
-/obj/machinery/turretid/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DisIntegrate(src)
- return TRUE
-
-/obj/machinery/chem_dispenser/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "The volatile chemicals in this machine would destroy us. Aborting.")
- return FALSE
-
-/obj/machinery/nuclearbomb/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "This device's destruction would result in the extermination of everything in the area. Aborting.")
- return FALSE
-
-/obj/effect/rune/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "Searching... sensor malfunction! Target lost. Aborting.")
- return FALSE
-
-/obj/structure/reagent_dispensers/fueltank/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "Destroying this object would cause a chain reaction. Aborting.")
- return FALSE
-
-/obj/structure/cable/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "Disrupting the power grid would bring no benefit to us. Aborting.")
- return FALSE
-
-/obj/machinery/portable_atmospherics/canister/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "An inhospitable area may be created as a result of destroying this object. Aborting.")
- return FALSE
-
-/obj/machinery/telecomms/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "This communications relay should be preserved, it will be a useful resource to our masters in the future. Aborting.")
- return FALSE
-
-/obj/machinery/deepfryer/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "This kitchen appliance should be preserved, it will make delicious unhealthy snacks for our masters in the future. Aborting.")
- return FALSE
-
-/obj/machinery/power/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "Disrupting the power grid would bring no benefit to us. Aborting.")
- return FALSE
-
-/obj/machinery/gateway/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "This bluespace source will be important to us later. Aborting.")
- return FALSE
-
-/turf/closed/wall/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- var/isonshuttle = istype(loc, /area/shuttle)
- for(var/turf/T in range(1, src))
- var/area/A = get_area(T)
- if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
- to_chat(S, "Destroying this object has the potential to cause a hull breach. Aborting.")
- S.target = null
- return TRUE
- else if(istype(A, /area/engine/supermatter))
- to_chat(S, "Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.")
- S.target = null
- return TRUE
- return ..()
-
-/obj/structure/window/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- var/isonshuttle = istype(get_area(src), /area/shuttle)
- for(var/turf/T in range(1, src))
- var/area/A = get_area(T)
- if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
- to_chat(S, "Destroying this object has the potential to cause a hull breach. Aborting.")
- S.target = null
- return TRUE
- else if(istype(A, /area/engine/supermatter))
- to_chat(S, "Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.")
- S.target = null
- return TRUE
- return ..()
-
-/obj/item/stack/cable_coil/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)//Wiring would be too effective as a resource
- to_chat(S, "This object does not contain enough materials to work with.")
- return FALSE
-
-/obj/machinery/porta_turret/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "Attempting to dismantle this machine would result in an immediate counterattack. Aborting.")
- return FALSE
-
-/obj/machinery/porta_turret_cover/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "Attempting to dismantle this machine would result in an immediate counterattack. Aborting.")
- return FALSE
-
-/mob/living/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- S.DisperseTarget(src)
- return TRUE
-
-/mob/living/simple_animal/slime/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "This biological resource is somehow resisting our bluespace transceiver. Aborting.")
- return FALSE
-
-/obj/machinery/drone_dispenser/swarmer/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "This object is receiving unactivated swarmer shells to help us. Aborting.")
- return FALSE
-
-/obj/structure/lattice/catwalk/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- var/turf/here = get_turf(src)
- for(var/A in here.contents)
- var/obj/structure/cable/C = A
- if(istype(C))
- to_chat(S, "Disrupting the power grid would bring no benefit to us. Aborting.")
- return FALSE
- return ..()
-
-/obj/item/deactivated_swarmer/IntegrateAmount()
- return 50
-
-/obj/machinery/hydroponics/soil/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "This object does not contain enough materials to work with.")
- return FALSE
-
-/obj/machinery/field/generator/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "Destroying this object would cause a catastrophic chain reaction. Aborting.")
- return FALSE
-
-/obj/machinery/field/containment/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "This object does not contain solid matter. Aborting.")
- return FALSE
-
-/obj/machinery/power/shieldwallgen/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "Destroying this object would have an unpredictable effect on structure integrity. Aborting.")
- return FALSE
-
-/obj/machinery/shieldwall/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- to_chat(S, "This object does not contain solid matter. Aborting.")
- return FALSE
-
-////END CTRL CLICK FOR SWARMERS////
-
-/mob/living/simple_animal/hostile/swarmer/proc/Fabricate(atom/fabrication_object,fabrication_cost = 0)
- if(!isturf(loc))
- to_chat(src, "This is not a suitable location for fabrication. We need more space.")
- if(resources >= fabrication_cost)
- resources -= fabrication_cost
- else
- to_chat(src, "You do not have the necessary resources to fabricate this object.")
- return
- return new fabrication_object(loc)
-
-/mob/living/simple_animal/hostile/swarmer/proc/Integrate(atom/movable/target)
- var/resource_gain = target.IntegrateAmount()
- if(resources + resource_gain > max_resources)
- to_chat(src, "We cannot hold more materials!")
- return TRUE
- if(resource_gain)
- resources += resource_gain
- do_attack_animation(target)
- changeNext_move(CLICK_CD_MELEE)
- var/obj/effect/temp_visual/swarmer/integrate/I = new /obj/effect/temp_visual/swarmer/integrate(get_turf(target))
- I.pixel_x = target.pixel_x
- I.pixel_y = target.pixel_y
- I.pixel_z = target.pixel_z
- if(istype(target, /obj/item/stack))
- var/obj/item/stack/S = target
- S.use(1)
- if(S.amount)
- return TRUE
- qdel(target)
- return TRUE
- else
- to_chat(src, "[target] is incompatible with our internal matter recycler.")
- return FALSE
-
-
-/mob/living/simple_animal/hostile/swarmer/proc/DisIntegrate(atom/movable/target)
- new /obj/effect/temp_visual/swarmer/disintegration(get_turf(target))
- do_attack_animation(target)
- changeNext_move(CLICK_CD_MELEE)
- SSexplosions.lowobj += target
-
-/mob/living/simple_animal/hostile/swarmer/proc/DisperseTarget(mob/living/target)
- if(target == src)
- return
-
- if(!is_station_level(z) && !is_mining_level(z))
- to_chat(src, "Our bluespace transceiver cannot locate a viable bluespace link, our teleportation abilities are useless in this area.")
- return
-
- to_chat(src, "Attempting to remove this being from our presence.")
-
- if(!do_mob(src, target, 30))
- return
-
- var/turf/open/floor/F
- F = find_safe_turf(zlevels = z, extended_safety_checks = TRUE)
-
- if(!F)
- return
- // If we're getting rid of a human, slap some energy cuffs on
- // them to keep them away from us a little longer
-
- var/mob/living/carbon/human/H = target
- if(ishuman(target) && (!H.handcuffed))
- H.handcuffed = new /obj/item/restraints/handcuffs/energy/used(H)
- H.update_handcuffed()
- log_combat(src, H, "handcuffed")
-
- var/datum/effect_system/spark_spread/S = new
- S.set_up(4,0,get_turf(target))
- S.start()
- playsound(src,'sound/effects/sparks4.ogg',50,TRUE)
- do_teleport(target, F, 0, channel = TELEPORT_CHANNEL_BLUESPACE)
-
-/mob/living/simple_animal/hostile/swarmer/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE)
- if(!(flags & SHOCK_TESLA))
- return FALSE
- return ..()
-
-/mob/living/simple_animal/hostile/swarmer/proc/DismantleMachine(obj/machinery/target)
- do_attack_animation(target)
- to_chat(src, "We begin to dismantle this machine. We will need to be uninterrupted.")
- var/obj/effect/temp_visual/swarmer/dismantle/D = new /obj/effect/temp_visual/swarmer/dismantle(get_turf(target))
- D.pixel_x = target.pixel_x
- D.pixel_y = target.pixel_y
- D.pixel_z = target.pixel_z
- if(do_mob(src, target, 100))
- to_chat(src, "Dismantling complete.")
- var/atom/Tsec = target.drop_location()
- new /obj/item/stack/sheet/metal(Tsec, 5)
- for(var/obj/item/I in target.component_parts)
- I.forceMove(Tsec)
- var/obj/effect/temp_visual/swarmer/disintegration/N = new /obj/effect/temp_visual/swarmer/disintegration(get_turf(target))
- N.pixel_x = target.pixel_x
- N.pixel_y = target.pixel_y
- N.pixel_z = target.pixel_z
- target.dropContents()
- if(istype(target, /obj/machinery/computer))
- var/obj/machinery/computer/C = target
- if(C.circuit)
- C.circuit.forceMove(Tsec)
- qdel(target)
-
-
-/obj/effect/temp_visual/swarmer //temporary swarmer visual feedback objects
- icon = 'icons/mob/swarmer.dmi'
- layer = BELOW_MOB_LAYER
-
-/obj/effect/temp_visual/swarmer/disintegration
- icon_state = "disintegrate"
- duration = 10
-
-/obj/effect/temp_visual/swarmer/disintegration/Initialize()
- . = ..()
- playsound(loc, "sparks", 100, TRUE)
-
-/obj/effect/temp_visual/swarmer/dismantle
- icon_state = "dismantle"
- duration = 25
-
-/obj/effect/temp_visual/swarmer/integrate
- icon_state = "integrate"
- duration = 5
-
-/obj/structure/swarmer //Default swarmer effect object visual feedback
- name = "swarmer ui"
- desc = null
- gender = NEUTER
- icon = 'icons/mob/swarmer.dmi'
- icon_state = "ui_light"
- layer = MOB_LAYER
- resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
- light_color = LIGHT_COLOR_CYAN
- max_integrity = 30
- anchored = TRUE
- var/lon_range = 1
-
-/obj/structure/swarmer/Initialize(mapload)
- . = ..()
- set_light(lon_range)
-
-/obj/structure/swarmer/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
- switch(damage_type)
- if(BRUTE)
- playsound(src, 'sound/weapons/egloves.ogg', 80, TRUE)
- if(BURN)
- playsound(src, 'sound/items/welder.ogg', 100, TRUE)
-
-/obj/structure/swarmer/emp_act()
- . = ..()
- if(. & EMP_PROTECT_SELF)
- return
- qdel(src)
-
-/obj/structure/swarmer/trap
- name = "swarmer trap"
- desc = "A quickly assembled trap that electrifies living beings and overwhelms machine sensors. Will not retain its form if damaged enough."
- icon_state = "trap"
- max_integrity = 10
- density = FALSE
-
-/obj/structure/swarmer/trap/Crossed(atom/movable/AM)
- if(isliving(AM))
- var/mob/living/L = AM
- if(!istype(L, /mob/living/simple_animal/hostile/swarmer))
- playsound(loc,'sound/effects/snap.ogg',50, TRUE, -1)
- L.electrocute_act(10, src, 1, flags = SHOCK_NOGLOVES|SHOCK_ILLUSION)
- if(iscyborg(L))
- L.Paralyze(100)
- qdel(src)
- ..()
-
-/mob/living/simple_animal/hostile/swarmer/proc/CreateTrap()
- set name = "Create trap"
- set category = "Swarmer"
- set desc = "Creates a simple trap that will non-lethally electrocute anything that steps on it. Costs 5 resources."
- if(locate(/obj/structure/swarmer/trap) in loc)
- to_chat(src, "There is already a trap here. Aborting.")
- return
- Fabricate(/obj/structure/swarmer/trap, 5)
-
-
-/mob/living/simple_animal/hostile/swarmer/proc/CreateBarricade()
- set name = "Create barricade"
- set category = "Swarmer"
- set desc = "Creates a barricade that will stop anything but swarmers and disabler beams from passing through."
- if(locate(/obj/structure/swarmer/blockade) in loc)
- to_chat(src, "There is already a blockade here. Aborting.")
- return
- if(resources < 5)
- to_chat(src, "We do not have the resources for this!")
- return
- if(do_mob(src, src, 10))
- Fabricate(/obj/structure/swarmer/blockade, 5)
-
-
-/obj/structure/swarmer/blockade
- name = "swarmer blockade"
- desc = "A quickly assembled energy blockade. Will not retain its form if damaged enough, but disabler beams and swarmers pass right through."
- icon_state = "barricade"
- light_range = MINIMUM_USEFUL_LIGHT_RANGE
- max_integrity = 50
- density = TRUE
-
-/obj/structure/swarmer/blockade/CanAllowThrough(atom/movable/O)
- . = ..()
- if(isswarmer(O))
- return TRUE
- if(istype(O, /obj/projectile/beam/disabler))
- return TRUE
-
-/mob/living/simple_animal/hostile/swarmer/proc/CreateSwarmer()
- set name = "Replicate"
- set category = "Swarmer"
- set desc = "Creates a shell for a new swarmer. Swarmers will self activate."
- to_chat(src, "We are attempting to replicate ourselves. We will need to stand still until the process is complete.")
- if(resources < 50)
- to_chat(src, "We do not have the resources for this!")
- return
- if(!isturf(loc))
- to_chat(src, "This is not a suitable location for replicating ourselves. We need more room.")
- return
- if(do_mob(src, src, 100))
- var/createtype = SwarmerTypeToCreate()
- if(createtype && Fabricate(createtype, 50))
- playsound(loc,'sound/items/poster_being_created.ogg',50, TRUE, -1)
-
-
-/mob/living/simple_animal/hostile/swarmer/proc/SwarmerTypeToCreate()
- return /obj/effect/mob_spawn/swarmer
-
-
-/mob/living/simple_animal/hostile/swarmer/proc/RepairSelf()
- set name = "Self Repair"
- set category = "Swarmer"
- set desc = "Attempts to repair damage to our body. You will have to remain motionless until repairs are complete."
- if(!isturf(loc))
- return
- to_chat(src, "Attempting to repair damage to our body, stand by...")
- if(do_mob(src, src, 100))
- adjustHealth(-100)
- to_chat(src, "We successfully repaired ourselves.")
-
-/mob/living/simple_animal/hostile/swarmer/proc/ToggleLight()
- if(!light_range)
- set_light(3)
- else
- set_light(0)
-
-/mob/living/simple_animal/hostile/swarmer/proc/swarmer_chat(msg)
- var/rendered = "Swarm communication - [src] [say_quote(msg)]"
- for(var/i in GLOB.mob_list)
- var/mob/M = i
- if(isswarmer(M))
- to_chat(M, rendered)
- if(isobserver(M))
- var/link = FOLLOW_LINK(M, src)
- to_chat(M, "[link] [rendered]")
-
-/mob/living/simple_animal/hostile/swarmer/proc/ContactSwarmers()
- var/message = stripped_input(src, "Announce to other swarmers", "Swarmer contact")
- // TODO get swarmers their own colour rather than just boldtext
- if(message)
- swarmer_chat(message)
+ if(!mind.has_antag_datum(/datum/antagonist/swarmer))
+ mind.add_antag_datum(/datum/antagonist/swarmer)
diff --git a/code/modules/antagonists/swarmer/swarmer_event.dm b/code/modules/antagonists/swarmer/swarmer_event.dm
deleted file mode 100644
index e086485a49c..00000000000
--- a/code/modules/antagonists/swarmer/swarmer_event.dm
+++ /dev/null
@@ -1,28 +0,0 @@
-/datum/round_event_control/spawn_swarmer
- name = "Spawn Swarmer Shell"
- typepath = /datum/round_event/spawn_swarmer
- weight = 7
- max_occurrences = 1 //Only once okay fam
- earliest_start = 30 MINUTES
- min_players = 15
-
-
-/datum/round_event/spawn_swarmer
-
-/datum/round_event/spawn_swarmer/start()
- if(find_swarmer())
- return 0
- if(!GLOB.the_gateway)
- return 0
- new /obj/effect/mob_spawn/swarmer(get_turf(GLOB.the_gateway))
- if(prob(25)) //25% chance to announce it to the crew
- var/swarmer_report = "[command_name()] High-Priority Update"
- swarmer_report += "
Our long-range sensors have detected an odd signal emanating from your station's gateway. We recommend immediate investigation of your gateway, as something may have come through."
- print_command_report(swarmer_report, announce=TRUE)
-
-/datum/round_event/spawn_swarmer/proc/find_swarmer()
- for(var/i in GLOB.mob_living_list)
- var/mob/living/L = i
- if(istype(L, /mob/living/simple_animal/hostile/swarmer) && L.client) //If there is a swarmer with an active client, we've found our swarmer
- return 1
- return 0
diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm
index 585ec34c4a9..d2979a59c91 100644
--- a/code/modules/antagonists/wizard/equipment/artefact.dm
+++ b/code/modules/antagonists/wizard/equipment/artefact.dm
@@ -293,7 +293,7 @@
to_chat(target, "You suddenly feel very hot!")
target.adjust_bodytemperature(50)
GiveHint(target)
- else if(is_pointed(I))
+ else if(I.get_sharpness() == SHARP_POINTY)
to_chat(target, "You feel a stabbing pain in [parse_zone(user.zone_selected)]!")
target.Paralyze(40)
GiveHint(target)
diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm
index b3e9714f5cb..49a422ca24d 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook.dm
@@ -533,7 +533,7 @@
/datum/spellbook_entry/summon/events
name = "Summon Events"
desc = "Give Murphy's law a little push and replace all events with special wizard ones that will confound and confuse everyone. Multiple castings increase the rate of these events."
- cost = 2
+ cost = 2
limit = 1
var/times = 0
@@ -579,6 +579,7 @@
desc = "An unearthly tome that glows with power."
icon = 'icons/obj/library.dmi'
icon_state ="book"
+ worn_icon_state = "book"
throw_speed = 2
throw_range = 5
w_class = WEIGHT_CLASS_TINY
diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm
index 70af1573f3e..6aa7110cb60 100644
--- a/code/modules/assembly/voice.dm
+++ b/code/modules/assembly/voice.dm
@@ -32,6 +32,10 @@
if(speaker == src)
return
+ // raw_message can contain multiple spaces between words etc which are not seen in chat due to HTML rendering
+ // this means if the teller records a message with e.g. double spaces or tabs, other people will not be able to trigger the sensor since they don't know how to perform the same combination
+ raw_message = htmlrendertext(raw_message)
+
if(listening && !radio_freq)
record_speech(speaker, raw_message, message_language)
else
diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm
index 337ed1db05f..4ce9dcf6fc0 100644
--- a/code/modules/asset_cache/asset_list.dm
+++ b/code/modules/asset_cache/asset_list.dm
@@ -45,7 +45,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
if (!ACI)
continue
.[asset_name] = ACI.url
-
+
// For registering or sending multiple others at once
/datum/asset/group
@@ -86,7 +86,9 @@ GLOBAL_LIST_EMPTY(asset_datums)
if (!name)
CRASH("spritesheet [type] cannot register without a name")
ensure_stripped()
-
+ for(var/size_id in sizes)
+ var/size = sizes[size_id]
+ register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
var/res_name = "spritesheet_[name].css"
var/fname = "data/spritesheets/[res_name]"
fdel(fname)
@@ -94,10 +96,6 @@ GLOBAL_LIST_EMPTY(asset_datums)
register_asset(res_name, fcopy_rsc(fname))
fdel(fname)
- for(var/size_id in sizes)
- var/size = sizes[size_id]
- register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
-
/datum/asset/spritesheet/send(client/C)
if (!name)
return
diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm
index 0462375f3dd..704977a4a17 100644
--- a/code/modules/asset_cache/asset_list_items.dm
+++ b/code/modules/asset_cache/asset_list_items.dm
@@ -6,12 +6,6 @@
"tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css',
)
-/datum/asset/group/tgui
- children = list(
- /datum/asset/simple/tgui,
- /datum/asset/simple/fontawesome
- )
-
/datum/asset/simple/headers
assets = list(
"alarm_green.gif" = 'icons/program_icons/alarm_green.gif',
diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm
index cc3b6510841..5fc436c7ca3 100644
--- a/code/modules/atmospherics/gasmixtures/reactions.dm
+++ b/code/modules/atmospherics/gasmixtures/reactions.dm
@@ -260,18 +260,18 @@ nobiliumsuppression = INFINITY
return cached_results["fire"] ? REACTING : NO_REACTION
//freon reaction (is not a fire yet)
-datum/gas_reaction/freonfire
+/datum/gas_reaction/freonfire
priority = -4
name = "Freon combustion"
id = "freonfire"
-datum/gas_reaction/freonfire/init_reqs()
+/datum/gas_reaction/freonfire/init_reqs()
min_requirements = list(
/datum/gas/oxygen = MINIMUM_MOLE_COUNT,
/datum/gas/freon = MINIMUM_MOLE_COUNT
)
-datum/gas_reaction/freonfire/react(datum/gas_mixture/air, datum/holder)
+/datum/gas_reaction/freonfire/react(datum/gas_mixture/air, datum/holder)
var/energy_released = 0
var/old_heat_capacity = air.heat_capacity()
var/list/cached_gases = air.gases //this speeds things up because accessing datum vars is slow
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
index c164dc5ae30..2fbfea08574 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
@@ -86,7 +86,7 @@
/obj/machinery/atmospherics/components/binary/circulator/wrench_act(mob/living/user, obj/item/I)
if(!panel_open)
return
- anchored = !anchored
+ set_anchored(!anchored)
I.play_tool_sound(src)
if(generator)
disconnectFromGenerator()
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index 50d3cdb600c..264caed3fa5 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -157,8 +157,8 @@
if(!parent)
WARNING("Component is missing a pipenet! Rebuilding...")
SSair.add_to_rebuild_queue(src)
- parent = parents[i]
- parent.update = 1
+ else
+ parent.update = 1
/obj/machinery/atmospherics/components/returnPipenets()
. = list()
diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm
index e9aad6d08ce..5cafed8516c 100644
--- a/code/modules/awaymissions/capture_the_flag.dm
+++ b/code/modules/awaymissions/capture_the_flag.dm
@@ -71,7 +71,7 @@
if(!user.put_in_active_hand(src))
dropped(user)
return
- user.anchored = TRUE
+ user.set_anchored(TRUE)
user.status_flags &= ~CANPUSH
for(var/mob/M in GLOB.player_list)
var/area/mob_area = get_area(M)
@@ -82,7 +82,7 @@
/obj/item/ctf/dropped(mob/user)
..()
- user.anchored = FALSE
+ user.set_anchored(FALSE)
user.status_flags |= CANPUSH
reset_cooldown = world.time + 200 //20 seconds
START_PROCESSING(SSobj, src)
diff --git a/code/modules/buildmode/buildmode.dm b/code/modules/buildmode/buildmode.dm
index 4a56257882a..735c2a4c045 100644
--- a/code/modules/buildmode/buildmode.dm
+++ b/code/modules/buildmode/buildmode.dm
@@ -34,7 +34,7 @@
holder.screen += buttons
holder.click_intercept = src
mode.enter_mode(src)
-
+
/datum/buildmode/proc/quit()
mode.exit_mode(src)
holder.screen -= buttons
@@ -100,7 +100,7 @@
else
close_switchstates()
open_modeswitch()
-
+
/datum/buildmode/proc/open_modeswitch()
switch_state = BM_SWITCHSTATE_MODE
holder.screen += modeswitch_buttons
@@ -115,7 +115,7 @@
else
close_switchstates()
open_dirswitch()
-
+
/datum/buildmode/proc/open_dirswitch()
switch_state = BM_SWITCHSTATE_DIR
holder.screen += dirswitch_buttons
@@ -155,7 +155,7 @@
new /datum/buildmode(M.client)
message_admins("[key_name_admin(usr)] has entered build mode.")
log_admin("[key_name(usr)] has entered build mode.")
-
+
#undef BM_SWITCHSTATE_NONE
#undef BM_SWITCHSTATE_MODE
#undef BM_SWITCHSTATE_DIR
diff --git a/code/modules/buildmode/submodes/delete.dm b/code/modules/buildmode/submodes/delete.dm
new file mode 100644
index 00000000000..ea2145a8dbd
--- /dev/null
+++ b/code/modules/buildmode/submodes/delete.dm
@@ -0,0 +1,62 @@
+/datum/buildmode_mode/delete
+ key = "delete"
+
+/datum/buildmode_mode/delete/show_help(client/c)
+ to_chat(c, "***********************************************************\n\
+ Left Mouse Button on anything to delete it. If you break it, you buy it.\n\
+ Right Mouse Button on anything to delete everything of the type. Probably don\'t do this unless you know what you are doing.\n\
+ ***********************************************************")
+
+/datum/buildmode_mode/delete/handle_click(client/c, params, object)
+ var/list/pa = params2list(params)
+ var/left_click = pa.Find("left")
+ var/right_click = pa.Find("right")
+
+ if(left_click)
+ if(isturf(object))
+ var/turf/T = object
+ T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
+ else if(isatom(object))
+ qdel(object)
+
+ if(right_click)
+ if(check_rights(R_DEBUG|R_SERVER)) //Prevents buildmoded non-admins from breaking everything.
+ if(isturf(object))
+ return
+ var/atom/deleting = object
+ var/action_type = alert("Strict type ([deleting.type]) or type and all subtypes?",,"Strict type","Type and subtypes","Cancel")
+ if(action_type == "Cancel" || !action_type)
+ return
+
+ if(alert("Are you really sure you want to delete all instances of type [deleting.type]?",,"Yes","No") != "Yes")
+ return
+
+ if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes")
+ return
+
+ var/O_type = deleting.type
+ switch(action_type)
+ if("Strict type")
+ var/i = 0
+ for(var/atom/Obj in world)
+ if(Obj.type == O_type)
+ i++
+ qdel(Obj)
+ CHECK_TICK
+ if(!i)
+ to_chat(usr, "No instances of this type exist")
+ return
+ log_admin("[key_name(usr)] deleted all instances of type [O_type] ([i] instances deleted) ")
+ message_admins("[key_name(usr)] deleted all instances of type [O_type] ([i] instances deleted) ")
+ if("Type and subtypes")
+ var/i = 0
+ for(var/Obj in world)
+ if(istype(Obj,O_type))
+ i++
+ qdel(Obj)
+ CHECK_TICK
+ if(!i)
+ to_chat(usr, "No instances of this type exist")
+ return
+ log_admin("[key_name(usr)] deleted all instances of type or subtype of [O_type] ([i] instances deleted) ")
+ message_admins("[key_name(usr)] deleted all instances of type or subtype of [O_type] ([i] instances deleted) ")
diff --git a/code/modules/buildmode/submodes/outfit.dm b/code/modules/buildmode/submodes/outfit.dm
new file mode 100644
index 00000000000..55ca0f4464d
--- /dev/null
+++ b/code/modules/buildmode/submodes/outfit.dm
@@ -0,0 +1,44 @@
+/datum/buildmode_mode/outfit
+ key = "outfit"
+ var/datum/outfit/dressuptime
+
+/datum/buildmode_mode/outfit/Destroy()
+ dressuptime = null
+ return ..()
+
+/datum/buildmode_mode/outfit/show_help(client/c)
+ to_chat(c, "***********************************************************\n\
+ Right Mouse Button on buildmode button = Select outfit to equip.\n\
+ Left Mouse Button on mob/living/carbon/human = Equip the selected outfit.\n\
+ Right Mouse Button on mob/living/carbon/human = Strip and delete current outfit.\n\
+ ***********************************************************")
+
+/datum/buildmode_mode/outfit/Reset()
+ . = ..()
+ dressuptime = null
+
+/datum/buildmode_mode/outfit/change_settings(client/c)
+ dressuptime = c.robust_dress_shop()
+
+/datum/buildmode_mode/outfit/handle_click(client/c, params, object)
+ var/list/pa = params2list(params)
+ var/left_click = pa.Find("left")
+ var/right_click = pa.Find("right")
+
+ if(!ishuman(object))
+ return
+ var/mob/living/carbon/human/dollie = object
+
+ if(left_click)
+ if(isnull(dressuptime))
+ to_chat(c, "Pick an outfit first.")
+ return
+
+ for (var/item in dollie.get_equipped_items(TRUE))
+ qdel(item)
+ if(dressuptime != "Naked")
+ dollie.equipOutfit(dressuptime)
+
+ if(right_click)
+ for (var/item in dollie.get_equipped_items(TRUE))
+ qdel(item)
diff --git a/code/modules/cargo/bounties/assistant.dm b/code/modules/cargo/bounties/assistant.dm
index e7b13006a7a..354f9d18954 100644
--- a/code/modules/cargo/bounties/assistant.dm
+++ b/code/modules/cargo/bounties/assistant.dm
@@ -130,6 +130,7 @@
reward = 4000
required_count = 3
wanted_types = list(/obj/item/reagent_containers/food/snacks/grown/poppy/geranium)
+ include_subtypes = FALSE
/datum/bounty/item/assistant/poppy
name = "Poppies"
@@ -152,19 +153,6 @@
required_count = 8
wanted_types = list(/obj/item/kirbyplants)
-/datum/bounty/item/assistant/earmuffs
- name = "Earmuffs"
- description = "Central Command is getting tired of your station's messages. They've ordered that you ship some earmuffs to lessen the annoyance."
- reward = 1000
- wanted_types = list(/obj/item/clothing/ears/earmuffs)
-
-/datum/bounty/item/assistant/handcuffs
- name = "Handcuffs"
- description = "A large influx of escaped convicts have arrived at Central Command. Now is the perfect time to ship out spare handcuffs (or restraints)."
- reward = 1000
- required_count = 5
- wanted_types = list(/obj/item/restraints/handcuffs)
-
/datum/bounty/item/assistant/monkey_cubes
name = "Monkey Cubes"
description = "Due to a recent genetics accident, Central Command is in serious need of monkeys. Your mission is to ship monkey cubes."
@@ -172,12 +160,6 @@
required_count = 3
wanted_types = list(/obj/item/reagent_containers/food/snacks/monkeycube)
-/datum/bounty/item/assistant/chainsaw
- name = "Chainsaw"
- description = "The chef at CentCom is having trouble butchering her animals. She requests one chainsaw, please."
- reward = 2500
- wanted_types = list(/obj/item/chainsaw)
-
/datum/bounty/item/assistant/ied
name = "IED"
description = "Nanotrasen's maximum security prison at CentCom is undergoing personnel training. Ship a handful of IEDs to serve as a training tools."
@@ -185,31 +167,12 @@
required_count = 3
wanted_types = list(/obj/item/grenade/iedcasing)
-/datum/bounty/item/assistant/bonfire
- name = "Lit Bonfire"
- description = "Space heaters are malfunctioning and the cargo crew of Central Command is starting to feel cold. Ship a lit bonfire to warm them up."
- reward = 5000
- wanted_types = list(/obj/structure/bonfire)
-
-/datum/bounty/item/assistant/bonfire/applies_to(obj/O)
- if(!..())
- return FALSE
- var/obj/structure/bonfire/B = O
- return !!B.burning
-
/datum/bounty/item/assistant/corgimeat
name = "Raw Corgi Meat"
description = "The Syndicate recently stole all of CentCom's Corgi meat. Ship out a replacement immediately."
reward = 3000
wanted_types = list(/obj/item/reagent_containers/food/snacks/meat/slab/corgi)
-/datum/bounty/item/assistant/corgifarming
- name = "Corgi Hides"
- description = "Admiral Weinstein's space yacht needs new upholstery. A dozen Corgi furs should do just fine."
- reward = 30000 //that's a lot of dead dogs
- required_count = 12
- wanted_types = list(/obj/item/stack/sheet/animalhide/corgi)
-
/datum/bounty/item/assistant/action_figures
name = "Action Figures"
description = "The vice president's son saw an ad for action figures on the telescreen and now he won't shut up about them. Ship some to ease his complaints."
@@ -217,12 +180,6 @@
required_count = 5
wanted_types = list(/obj/item/toy/figure)
-/datum/bounty/item/assistant/tail_whip
- name = "Nine Tails whip"
- description = "Commander Jackson is looking for a fine addition to her exotic weapons collection. She will reward you handsomely for either a Cat or Liz o' Nine Tails."
- reward = 4000
- wanted_types = list(/obj/item/melee/chainofcommand/tailwhip)
-
/datum/bounty/item/assistant/dead_mice
name = "Dead Mice"
description = "Station 14 ran out of freeze-dried mice. Ship some fresh ones so their janitor doesn't go on strike."
diff --git a/code/modules/cargo/bounties/botany.dm b/code/modules/cargo/bounties/botany.dm
index 0a162f3fa72..e924aa42931 100644
--- a/code/modules/cargo/bounties/botany.dm
+++ b/code/modules/cargo/bounties/botany.dm
@@ -64,7 +64,7 @@
multiplier = 4 //hush money
bonus_desc = "Do not mention this shipment to security."
foodtype = "batch of \"muffins\""
-
+
/datum/bounty/item/botany/cannabis_white
name = "Lifeweed Leaves"
wanted_types = list(/obj/item/reagent_containers/food/snacks/grown/cannabis/white)
@@ -199,3 +199,14 @@
multiplier = 2
foodtype = "batch of oatmeal"
bonus_desc = "Squats and oats. We're all out of oats."
+
+/datum/bounty/item/botany/bonfire
+ name = "Lit Bonfire"
+ description = "Space heaters are malfunctioning and the cargo crew of Central Command is starting to feel cold. Grow some logs and Ship a lit bonfire to warm them up."
+ wanted_types = list(/obj/structure/bonfire)
+
+/datum/bounty/item/botany/bonfire/applies_to(obj/O)
+ if(!..())
+ return FALSE
+ var/obj/structure/bonfire/B = O
+ return !!B.burning
diff --git a/code/modules/cargo/bounties/chef.dm b/code/modules/cargo/bounties/chef.dm
index d0e946ba2a6..ce51de70f26 100644
--- a/code/modules/cargo/bounties/chef.dm
+++ b/code/modules/cargo/bounties/chef.dm
@@ -136,3 +136,9 @@
required_count = 6
wanted_types = list(/obj/item/reagent_containers/food/snacks/nugget)
+/datum/bounty/item/chef/corgifarming //Butchering is a chef's job.
+ name = "Corgi Hides"
+ description = "Admiral Weinstein's space yacht needs new upholstery. A dozen Corgi furs should do just fine."
+ reward = 30000 //that's a lot of dead dogs
+ required_count = 12
+ wanted_types = list(/obj/item/stack/sheet/animalhide/corgi)
diff --git a/code/modules/cargo/bounties/engineering.dm b/code/modules/cargo/bounties/engineering.dm
index 750889c4cee..63e0b56d2ef 100644
--- a/code/modules/cargo/bounties/engineering.dm
+++ b/code/modules/cargo/bounties/engineering.dm
@@ -16,22 +16,22 @@
/datum/bounty/item/engineering/gas/nitryl_tank
name = "Full Tank of Nitryl"
- description = "The non-human staff of Station 88 has been volunteered to test performance enhancing drugs. Ship them a tank full of Nitryl so they can get started."
+ description = "The non-human staff of Station 88 has been volunteered to test performance enhancing drugs. Ship them a tank full of Nitryl so they can get started. (20 Moles)"
gas_type = /datum/gas/nitryl
/datum/bounty/item/engineering/gas/freon_tank
name = "Full Tank of Freon"
- description = "The Supermatter of station 33 has started the delamination process. Deliver a tank of Freon gas to help them stop it!"
+ description = "The Supermatter of station 33 has started the delamination process. Deliver a tank of Freon gas to help them stop it! (20 Moles)"
gas_type = /datum/gas/freon
/datum/bounty/item/engineering/gas/tritium_tank
name = "Full Tank of Tritium"
- description = "Station 49 is looking to kickstart their research program. Ship them a tank full of Tritium."
+ description = "Station 49 is looking to kickstart their research program. Ship them a tank full of Tritium. (20 Moles)"
gas_type = /datum/gas/tritium
/datum/bounty/item/engineering/gas/hydrogen_tank
name = "Full Tank of Hydrogen"
- description = "Our R&D department is working on the development of more efficient electrical batteries using hydrogen as a catalyst. Ship us a tank full of it."
+ description = "Our R&D department is working on the development of more efficient electrical batteries using hydrogen as a catalyst. Ship us a tank full of it. (20 Moles)"
gas_type = /datum/gas/hydrogen
/datum/bounty/item/engineering/energy_ball
@@ -45,3 +45,15 @@
return FALSE
var/obj/singularity/energy_ball/T = O
return !T.miniball
+
+/datum/bounty/item/engineering/emitter
+ name = "Emitter"
+ description = "We think there may be a defect in your station's emitter designs, based on the sheer number of delaminations your sector seems to see. Ship us one of yours."
+ reward = 2500
+ wanted_types = list(/obj/machinery/power/emitter)
+
+/datum/bounty/item/engineering/hydro_tray
+ name = "Hydroponics Tray"
+ description = "The lab technicians are trying to figure out how to lower the power drain of hydroponics trays, but we fried our last one. Mind building one for us?"
+ reward = 2000
+ wanted_types = list(/obj/machinery/hydroponics/constructable)
diff --git a/code/modules/cargo/bounties/medical.dm b/code/modules/cargo/bounties/medical.dm
index d1397759695..3f737448a5b 100644
--- a/code/modules/cargo/bounties/medical.dm
+++ b/code/modules/cargo/bounties/medical.dm
@@ -57,3 +57,15 @@
description = "Central Command has run out of heavy duty pipe cleaners. Can you ship over a cat tail to help us out?"
reward = 3000
wanted_types = list(/obj/item/organ/tail/cat)
+
+/datum/bounty/item/medical/chainsaw
+ name = "Chainsaw"
+ description = "A CMO at CentCom is having trouble operating on golems. She requests one chainsaw, please."
+ reward = 2500
+ wanted_types = list(/obj/item/chainsaw)
+
+/datum/bounty/item/medical/tail_whip //Like the cat tail bounties, with more processing.
+ name = "Nine Tails whip"
+ description = "Commander Jackson is looking for a fine addition to her exotic weapons collection. She will reward you handsomely for either a Cat or Liz o' Nine Tails."
+ reward = 4000
+ wanted_types = list(/obj/item/melee/chainofcommand/tailwhip)
diff --git a/code/modules/cargo/bounties/reagent.dm b/code/modules/cargo/bounties/reagent.dm
index 6e38a0c2be1..62aefe87634 100644
--- a/code/modules/cargo/bounties/reagent.dm
+++ b/code/modules/cargo/bounties/reagent.dm
@@ -233,7 +233,7 @@
//reagent that are possible to be chem factory'd
var/static/list/possible_reagents = list(\
/datum/reagent/medicine/spaceacillin,\
- /datum/reagent/medicine/c2/instabitaluri,\
+ /datum/reagent/medicine/c2/synthflesh,\
/datum/reagent/medicine/pen_acid,\
/datum/reagent/medicine/atropine,\
/datum/reagent/medicine/cryoxadone,\
diff --git a/code/modules/cargo/bounties/science.dm b/code/modules/cargo/bounties/science.dm
index dbe64e61e48..c503ee2b100 100644
--- a/code/modules/cargo/bounties/science.dm
+++ b/code/modules/cargo/bounties/science.dm
@@ -64,3 +64,9 @@
description = "With the price of rechargers on the rise, upper management is interested in purchasing guns that are self-powered. If you ship one, they'll pay."
reward = 10000
wanted_types = list(/obj/item/gun/energy/e_gun/nuclear)
+
+/datum/bounty/item/science/bepis_disc
+ name = "Reformatted Tech Disk"
+ description = "It turns out the diskettes the BEPIS prints experimental nodes on are extremely space-efficient. Send us one of your spares when you're done with it."
+ reward = 4000
+ wanted_types = list(/obj/item/disk/tech_disk/major)
diff --git a/code/modules/cargo/bounties/security.dm b/code/modules/cargo/bounties/security.dm
index bcf7b89f3af..28b62be1f4e 100644
--- a/code/modules/cargo/bounties/security.dm
+++ b/code/modules/cargo/bounties/security.dm
@@ -11,3 +11,37 @@
reward = 2000
required_count = 3
wanted_types = list(/obj/machinery/recharger)
+
+/datum/bounty/item/security/pepperspray
+ name = "Pepperspray"
+ description = "We've been having a bad run of riots on Space Station 76. We could use some new pepperspray cans."
+ reward = 3000
+ required_count = 4
+ wanted_types = list(/obj/item/reagent_containers/spray/pepper)
+
+/datum/bounty/item/security/prison_clothes
+ name = "Prison Uniforms"
+ description = "Terragov has been unable to source any new prisoner uniforms, so if you have any spares, we'll take them off your hands."
+ reward = 2000
+ required_count = 4
+ wanted_types = list(/obj/item/clothing/under/rank/prisoner)
+
+/datum/bounty/item/security/plates
+ name = "License Plates"
+ description = "As a result of a bad clown car crash, we could use an advance on some of your prisoner's license plates."
+ reward = 1000
+ required_count = 10
+ wanted_types = list(/obj/item/stack/license_plates/filled)
+
+/datum/bounty/item/security/earmuffs
+ name = "Earmuffs"
+ description = "Central Command is getting tired of your station's messages. They've ordered that you ship some earmuffs to lessen the annoyance."
+ reward = 1000
+ wanted_types = list(/obj/item/clothing/ears/earmuffs)
+
+/datum/bounty/item/security/handcuffs
+ name = "Handcuffs"
+ description = "A large influx of escaped convicts have arrived at Central Command. Now is the perfect time to ship out spare handcuffs (or restraints)."
+ reward = 1000
+ required_count = 5
+ wanted_types = list(/obj/item/restraints/handcuffs)
diff --git a/code/modules/cargo/bounty.dm b/code/modules/cargo/bounty.dm
index 447a573a5a9..ce281bdfcee 100644
--- a/code/modules/cargo/bounty.dm
+++ b/code/modules/cargo/bounty.dm
@@ -7,18 +7,18 @@ GLOBAL_LIST_EMPTY(bounties_list)
var/claimed = FALSE
var/high_priority = FALSE
-// Displayed on bounty UI screen.
+/// Displayed on bounty UI screen.
/datum/bounty/proc/completion_string()
return ""
-// Displayed on bounty UI screen.
+/// Displayed on bounty UI screen.
/datum/bounty/proc/reward_string()
return "[reward] Credits"
/datum/bounty/proc/can_claim()
return !claimed
-// Called when the claim button is clicked. Override to provide fancy rewards.
+/// Called when the claim button is clicked. Override to provide fancy rewards.
/datum/bounty/proc/claim()
if(can_claim())
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
@@ -26,16 +26,17 @@ GLOBAL_LIST_EMPTY(bounties_list)
D.adjust_money(reward)
claimed = TRUE
-// If an item sent in the cargo shuttle can satisfy the bounty.
+/// If an item sent in the cargo shuttle can satisfy the bounty.
/datum/bounty/proc/applies_to(obj/O)
return FALSE
-// Called when an object is shipped on the cargo shuttle.
+/// Called when an object is shipped on the cargo shuttle.
/datum/bounty/proc/ship(obj/O)
return
-// When randomly generating the bounty list, duplicate bounties must be avoided.
-// This proc is used to determine if two bounties are duplicates, or incompatible in general.
+/** When randomly generating the bounty list, duplicate bounties must be avoided.
+ * This proc is used to determine if two bounties are duplicates, or incompatible in general.
+ */
/datum/bounty/proc/compatible_with(other_bounty)
return TRUE
@@ -45,8 +46,9 @@ GLOBAL_LIST_EMPTY(bounties_list)
high_priority = TRUE
reward = round(reward * scale_reward)
-// This proc is called when the shuttle docks at CentCom.
-// It handles items shipped for bounties.
+/** This proc is called when the shuttle docks at CentCom.
+ * It handles items shipped for bounties.
+ */
/proc/bounty_ship_item_and_contents(atom/movable/AM, dry_run=FALSE)
if(!GLOB.bounties_list.len)
setup_bounties()
@@ -64,7 +66,7 @@ GLOBAL_LIST_EMPTY(bounties_list)
qdel(thing)
return matched_one
-// Returns FALSE if the bounty is incompatible with the current bounties.
+/// Returns FALSE if the bounty is incompatible with the current bounties.
/proc/try_add_bounty(datum/bounty/new_bounty)
if(!new_bounty || !new_bounty.name || !new_bounty.description)
return FALSE
@@ -75,9 +77,17 @@ GLOBAL_LIST_EMPTY(bounties_list)
GLOB.bounties_list += new_bounty
return TRUE
-// Returns a new bounty of random type, but does not add it to GLOB.bounties_list.
-/proc/random_bounty()
- switch(rand(1, 13))
+/** Returns a new bounty of random type, but does not add it to GLOB.bounties_list.
+ *
+ * *Guided determines what specific catagory of bounty should be chosen.
+ */
+/proc/random_bounty(var/guided = 0)
+ var/bounty_num
+ if(guided)
+ bounty_num = guided
+ else
+ bounty_num = rand(1,13)
+ switch(bounty_num)
if(1)
var/subtype = pick(subtypesof(/datum/bounty/item/assistant))
return new subtype
@@ -102,21 +112,21 @@ GLOBAL_LIST_EMPTY(bounties_list)
var/subtype = pick(subtypesof(/datum/bounty/virus))
return new subtype
if(8)
- var/subtype = pick(subtypesof(/datum/bounty/item/science))
- return new subtype
- if(9)
+ if(rand(2) == 1)
+ var/subtype = pick(subtypesof(/datum/bounty/item/science))
+ return new subtype
var/subtype = pick(subtypesof(/datum/bounty/item/slime))
return new subtype
- if(10)
+ if(9)
var/subtype = pick(subtypesof(/datum/bounty/item/engineering))
return new subtype
- if(11)
+ if(10)
var/subtype = pick(subtypesof(/datum/bounty/item/mining))
return new subtype
- if(12)
+ if(11)
var/subtype = pick(subtypesof(/datum/bounty/item/medical))
return new subtype
- if(13)
+ if(12)
var/subtype = pick(subtypesof(/datum/bounty/item/botany))
return new subtype
diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm
index 075194e2fff..fed098b4f30 100644
--- a/code/modules/cargo/centcom_podlauncher.dm
+++ b/code/modules/cargo/centcom_podlauncher.dm
@@ -608,7 +608,7 @@
else
toLaunch.reverse_dropoff_turf = bay //Bay is currently a nonstatic expression, so it cant go into toLaunch using DuplicateObject
toLaunch.update_icon()//we update_icon() here so that the door doesnt "flicker on" right after it lands
- var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/fly_me_to_the_moon]
+ var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding]
toLaunch.forceMove(shippingLane)
if (launchClone) //We arent launching the actual items from the bay, rather we are creating clones and launching those
if(launchRandomItem)
diff --git a/code/modules/cargo/coupon.dm b/code/modules/cargo/coupon.dm
index 80df3684ef4..ac77a994a03 100644
--- a/code/modules/cargo/coupon.dm
+++ b/code/modules/cargo/coupon.dm
@@ -1,6 +1,6 @@
#define COUPON_OMEN "omen"
-obj/item/coupon
+/obj/item/coupon
name = "coupon"
desc = "It doesn't matter if you didn't want it before, what matters now is that you've got a coupon for it!"
icon_state = "data_1"
diff --git a/code/modules/cargo/exports/large_objects.dm b/code/modules/cargo/exports/large_objects.dm
index 161b98362ed..1afbc87285e 100644
--- a/code/modules/cargo/exports/large_objects.dm
+++ b/code/modules/cargo/exports/large_objects.dm
@@ -126,18 +126,19 @@
cost = 10 //Base cost of canister. You get more for nice gases inside.
unit_name = "Gas Canister"
export_types = list(/obj/machinery/portable_atmospherics/canister)
+
/datum/export/large/gas_canister/get_cost(obj/O)
var/obj/machinery/portable_atmospherics/canister/C = O
var/worth = 10
var/gases = C.air_contents.gases
C.air_contents.assert_gases(/datum/gas/bz,/datum/gas/stimulum,/datum/gas/hypernoblium,/datum/gas/miasma,/datum/gas/tritium,/datum/gas/pluoxium,/datum/gas/freon,/datum/gas/hydrogen)
- worth += gases[/datum/gas/bz][MOLES]*4
- worth += gases[/datum/gas/stimulum][MOLES]*100
worth += gases[/datum/gas/hypernoblium][MOLES]*1000
- worth += gases[/datum/gas/miasma][MOLES]*10
+ worth += gases[/datum/gas/stimulum][MOLES]*100
+ worth += gases[/datum/gas/freon][MOLES]*15
worth += gases[/datum/gas/tritium][MOLES]*5
worth += gases[/datum/gas/pluoxium][MOLES]*5
- worth += gases[/datum/gas/freon][MOLES]*15
+ worth += gases[/datum/gas/bz][MOLES]*4
+ worth += gases[/datum/gas/miasma][MOLES]*2
worth += gases[/datum/gas/hydrogen][MOLES]*1
return worth
diff --git a/code/modules/cargo/exports/parts.dm b/code/modules/cargo/exports/parts.dm
index 0df08954398..115dfdf3ff6 100644
--- a/code/modules/cargo/exports/parts.dm
+++ b/code/modules/cargo/exports/parts.dm
@@ -15,11 +15,6 @@
unit_name = "solar panel control board"
export_types = list(/obj/item/circuitboard/computer/solar_control)
-/datum/export/swarmer
- cost = 2000
- unit_name = "deactivated alien deconstruction drone"
- export_types = list(/obj/item/deactivated_swarmer)
-
//Computer Tablets and Parts
/datum/export/modular_part
cost = 15
diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm
index 866f270c259..5e71e7f7972 100644
--- a/code/modules/cargo/expressconsole.dm
+++ b/code/modules/cargo/expressconsole.dm
@@ -1,4 +1,4 @@
-#define MAX_EMAG_ROCKETS 8
+#define MAX_EMAG_ROCKETS 5
#define BEACON_COST 500
#define SP_LINKED 1
#define SP_READY 2
@@ -149,6 +149,9 @@
if("add")//Generate Supply Order first
+ if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_EXPRESSPOD_CONSOLE))
+ say("Railgun recalibrating. Stand by.")
+ return
var/id = text2path(params["id"])
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
if(!istype(pack))
@@ -189,6 +192,7 @@
if(empty_turfs && empty_turfs.len)
LZ = pick(empty_turfs)
if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call
+ TIMER_COOLDOWN_START(src, COOLDOWN_EXPRESSPOD_CONSOLE, 5 SECONDS)
D.adjust_money(-SO.pack.cost)
new /obj/effect/pod_landingzone(LZ, podType, SO)
. = TRUE
@@ -202,6 +206,7 @@
LAZYADD(empty_turfs, T)
CHECK_TICK
if(empty_turfs && empty_turfs.len)
+ TIMER_COOLDOWN_START(src, COOLDOWN_EXPRESSPOD_CONSOLE, 10 SECONDS)
D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS)))
SO.generateRequisition(get_turf(src))
diff --git a/code/modules/cargo/goodies.dm b/code/modules/cargo/goodies.dm
index a942d20db69..c071cbb4753 100644
--- a/code/modules/cargo/goodies.dm
+++ b/code/modules/cargo/goodies.dm
@@ -129,3 +129,9 @@
desc = "The simple beach ball is one of Nanotrasen's most popular products. 'Why do we make beach balls? Because we can! (TM)' - Nanotrasen"
cost = 200
contains = list(/obj/item/toy/beach_ball)
+
+/datum/supply_pack/goody/medipen_twopak
+ name = "Medipen Two-Pak"
+ desc = "Contains one standard epinephrine medipen and one standard emergency first-aid kit medipen. For when you want to prepare for the worst."
+ cost = 500
+ contains = list(/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/hypospray/medipen/ekit)
diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm
index 8833d3b9c9f..37a42049891 100644
--- a/code/modules/cargo/packs.dm
+++ b/code/modules/cargo/packs.dm
@@ -1160,6 +1160,21 @@
crate_name = "blood freezer"
crate_type = /obj/structure/closet/crate/freezer
+/datum/supply_pack/medical/medipen_variety
+ name = "Medipen Variety-Pak"
+ desc = "Contains eight different medipens in three different varieties, to assist in quickly treating seriously injured patients."
+ cost = 2000
+ contains = list(/obj/item/reagent_containers/hypospray/medipen/,
+ /obj/item/reagent_containers/hypospray/medipen/,
+ /obj/item/reagent_containers/hypospray/medipen/ekit,
+ /obj/item/reagent_containers/hypospray/medipen/ekit,
+ /obj/item/reagent_containers/hypospray/medipen/ekit,
+ /obj/item/reagent_containers/hypospray/medipen/blood_loss,
+ /obj/item/reagent_containers/hypospray/medipen/blood_loss,
+ /obj/item/reagent_containers/hypospray/medipen/blood_loss
+)
+ crate_name = "medipen crate"
+
/datum/supply_pack/medical/chemical
name = "Chemical Starter Kit Crate"
desc = "Contains thirteen different chemicals, for all the fun experiments you can make."
@@ -2267,15 +2282,14 @@
crate_name = "toy crate"
crate_type = /obj/structure/closet/crate/wooden
-/datum/supply_pack/costumes_toys/randomised/toys/generate()
- . = ..()
+/datum/supply_pack/costumes_toys/randomised/toys/fill(obj/structure/closet/crate/C)
var/the_toy
for(var/i in 1 to num_contained)
if(prob(50))
the_toy = pickweight(GLOB.arcade_prize_pool)
else
the_toy = pick(subtypesof(/obj/item/toy/plush))
- new the_toy(.)
+ new the_toy(C)
/datum/supply_pack/costumes_toys/wizard
name = "Wizard Costume Crate"
@@ -2383,12 +2397,11 @@
contains = list()
crate_name = "booster pack pack"
-/datum/supply_pack/costumes_toys/randomised/tcg/generate()
- . = ..()
+/datum/supply_pack/costumes_toys/randomised/tcg/fill(obj/structure/closet/crate/C)
var/cardpacktype
for(var/i in 1 to 10)
cardpacktype = pick(subtypesof(/obj/item/cardpack))
- new cardpacktype(.)
+ new cardpacktype(C)
//////////////////////////////////////////////////////////////////////////////
//////////////////////////// Miscellaneous ///////////////////////////////////
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index d36ebc94988..10b000af4d2 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -80,8 +80,11 @@
landingDelay = 20 //Very speedy!
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
-/obj/structure/closet/supplypod/Initialize()
+/obj/structure/closet/supplypod/Initialize(var/turf/spawn_location)
. = ..()
+ if (!spawn_location)
+ var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding] //temporary holder for supplypods mid-transit
+ forceMove(shippingLane)
setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly
/obj/structure/closet/supplypod/extractionpod/Initialize()
@@ -184,7 +187,7 @@
stay_after_drop = FALSE
holder.pixel_z = initial(holder.pixel_z)
holder.alpha = initial(holder.alpha)
- var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/fly_me_to_the_moon]
+ var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding]
forceMove(shippingLane) //Move to the centcom-z-level until the pod_landingzone says we can drop back down again
if (!reverse_dropoff_turf) //If we're centcom-launched, the reverse dropoff turf will be a centcom loading bay. If we're an extraction pod, it should be the ninja jail.
reverse_dropoff_turf = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 17216de90de..6e71c8a2ec6 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -28,9 +28,15 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/UI_style = null
var/buttons_locked = FALSE
var/hotkeys = TRUE
+
+ ///Runechat preference. If true, certain messages will be displayed on the map, not ust on the chat area. Boolean.
var/chat_on_map = TRUE
+ ///Limit preference on the size of the message. Requires chat_on_map to have effect.
var/max_chat_length = CHAT_MESSAGE_MAX_LENGTH
+ ///Whether non-mob messages will be displayed, such as machine vendor announcements. Requires chat_on_map to have effect. Boolean.
var/see_chat_non_mob = TRUE
+ ///Whether emotes will be displayed on runechat. Requires chat_on_map to have effect. Boolean.
+ var/see_rc_emotes = TRUE
// Custom Keybindings
var/list/key_bindings = list()
@@ -295,7 +301,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += " Backpack: [backpack]"
dat += "[(randomise[RANDOM_BACKPACK]) ? "Lock" : "Unlock"]"
- if(CAN_SCAR in pref_species.species_traits)
+ if((HAS_FLESH in pref_species.species_traits) || (HAS_BONE in pref_species.species_traits))
dat += " Temporal Scarring: [(persistent_scars) ? "Enabled" : "Disabled"]"
dat += "Clear scar slots"
@@ -553,6 +559,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Show Runechat Chat Bubbles:[chat_on_map ? "Enabled" : "Disabled"] "
dat += "Runechat message char limit:[max_chat_length] "
dat += "See Runechat for non-mobs:[see_chat_non_mob ? "Enabled" : "Disabled"] "
+ dat += "See Runechat emotes:[see_rc_emotes ? "Enabled" : "Disabled"] "
dat += " "
dat += "Action Buttons:[(buttons_locked) ? "Locked In Place" : "Unlocked"] "
dat += "Hotkey mode:[(hotkeys) ? "Hotkeys" : "Default"] "
@@ -1655,6 +1662,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
chat_on_map = !chat_on_map
if("see_chat_non_mob")
see_chat_non_mob = !see_chat_non_mob
+ if("see_rc_emotes")
+ see_rc_emotes = !see_rc_emotes
if("action_buttons")
buttons_locked = !buttons_locked
@@ -1959,15 +1968,3 @@ GLOBAL_LIST_EMPTY(preferences_datums)
return
else
custom_names[name_id] = sanitized_name
-
-//Used in savefile update 32, can be removed once that is no longer relevant.
-/datum/preferences/proc/force_reset_keybindings()
- var/choice = tgalert(parent.mob, "Your basic keybindings need to be reset, emotes will remain as before. Would you prefer 'hotkey' or 'classic' mode?", "Reset keybindings", "Hotkey", "Classic")
- hotkeys = (choice != "Classic")
- var/list/oldkeys = key_bindings
- key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key)
-
- for(var/key in oldkeys)
- if(!key_bindings[key])
- key_bindings[key] = oldkeys[key]
- parent.update_movement_keys()
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 9c0ee2584fc..9984fd0b366 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -1,11 +1,11 @@
//This is the lowest supported version, anything below this is completely obsolete and the entire savefile will be wiped.
-#define SAVEFILE_VERSION_MIN 18
+#define SAVEFILE_VERSION_MIN 32
//This is the current version, anything below this will attempt to update (if it's not obsolete)
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
-#define SAVEFILE_VERSION_MAX 34
+#define SAVEFILE_VERSION_MAX 36
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -25,7 +25,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
*/
/datum/preferences/proc/savefile_needs_update(savefile/S)
var/savefile_version
- S["version"] >> savefile_version
+ READ_FILE(S["version"], savefile_version)
if(savefile_version < SAVEFILE_VERSION_MIN)
S.dir.Cut()
@@ -42,105 +42,36 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//if your savefile is 3 months out of date, then 'tough shit'.
/datum/preferences/proc/update_preferences(current_version, savefile/S)
- if(current_version < 30)
- if(clientfps == 0)
- clientfps = 60
-
- if(current_version < 31)
- if(clientfps == 60)
- clientfps = 0
-
- if(current_version < 32) //If you remove this, remove force_reset_keybindings() too.
- addtimer(CALLBACK(src, .proc/force_reset_keybindings), 30) //No mob available when this is run, timer allows user choice.
-
if(current_version < 33)
toggles |= SOUND_ENDOFROUND
if(current_version < 34)
auto_fit_viewport = TRUE
+ if(current_version < 35) //makes old keybinds compatible with #52040, sets the new default
+ var/newkey = FALSE
+ for(var/list/key in key_bindings)
+ for(var/bind in key)
+ if(bind == "quick_equipbelt")
+ key -= "quick_equipbelt"
+ key |= "quick_equip_belt"
+
+ if(bind == "bag_equip")
+ key -= "bag_equip"
+ key |= "quick_equip_bag"
+
+ if(bind == "quick_equip_suit_storage")
+ newkey = TRUE
+ if(!newkey && !key_bindings["ShiftQ"])
+ key_bindings["ShiftQ"] = list("quick_equip_suit_storage")
+
+ if(current_version < 36)
+ if(key_bindings["ShiftQ"] == "quick_equip_suit_storage")
+ key_bindings["ShiftQ"] = list("quick_equip_suit_storage")
+
+
/datum/preferences/proc/update_character(current_version, savefile/S)
- if(current_version < 19)
- pda_style = "mono"
- if(current_version < 20)
- pda_color = "#808000"
- if((current_version < 21) && features["ethcolor"] && (features["ethcolor"] == "#9c3030"))
- features["ethcolor"] = "9c3030"
- if(current_version < 22)
- job_preferences = list() //It loaded null from nonexistent savefile field.
- var/job_civilian_high = 0
- var/job_civilian_med = 0
- var/job_civilian_low = 0
-
- var/job_medsci_high = 0
- var/job_medsci_med = 0
- var/job_medsci_low = 0
-
- var/job_engsec_high = 0
- var/job_engsec_med = 0
- var/job_engsec_low = 0
-
- S["job_civilian_high"] >> job_civilian_high
- S["job_civilian_med"] >> job_civilian_med
- S["job_civilian_low"] >> job_civilian_low
- S["job_medsci_high"] >> job_medsci_high
- S["job_medsci_med"] >> job_medsci_med
- S["job_medsci_low"] >> job_medsci_low
- S["job_engsec_high"] >> job_engsec_high
- S["job_engsec_med"] >> job_engsec_med
- S["job_engsec_low"] >> job_engsec_low
-
- //Can't use SSjob here since this happens right away on login
- for(var/job in subtypesof(/datum/job))
- var/datum/job/J = job
- var/new_value
- var/fval = initial(J.flag)
- switch(initial(J.department_flag))
- if(CIVILIAN)
- if(job_civilian_high & fval)
- new_value = JP_HIGH
- else if(job_civilian_med & fval)
- new_value = JP_MEDIUM
- else if(job_civilian_low & fval)
- new_value = JP_LOW
- if(MEDSCI)
- if(job_medsci_high & fval)
- new_value = JP_HIGH
- else if(job_medsci_med & fval)
- new_value = JP_MEDIUM
- else if(job_medsci_low & fval)
- new_value = JP_LOW
- if(ENGSEC)
- if(job_engsec_high & fval)
- new_value = JP_HIGH
- else if(job_engsec_med & fval)
- new_value = JP_MEDIUM
- else if(job_engsec_low & fval)
- new_value = JP_LOW
- if(new_value)
- job_preferences[initial(J.title)] = new_value
- if(current_version < 23)
- if(all_quirks)
- all_quirks -= "Physically Obstructive"
- all_quirks -= "Neat"
- all_quirks -= "NEET"
- if(current_version < 24)
- if (!(underwear in GLOB.underwear_list))
- underwear = "Nude"
- if(current_version < 25)
- randomise = list(RANDOM_UNDERWEAR = TRUE, RANDOM_UNDERWEAR_COLOR = TRUE, RANDOM_UNDERSHIRT = TRUE, RANDOM_SOCKS = TRUE, RANDOM_BACKPACK = TRUE, RANDOM_JUMPSUIT_STYLE = TRUE, RANDOM_HAIRSTYLE = TRUE, RANDOM_HAIR_COLOR = TRUE, RANDOM_FACIAL_HAIRSTYLE = TRUE, RANDOM_FACIAL_HAIR_COLOR = TRUE, RANDOM_SKIN_TONE = TRUE, RANDOM_EYE_COLOR = TRUE)
- if(S["name_is_always_random"] == 1)
- randomise[RANDOM_NAME] = TRUE
- if(S["body_is_always_random"] == 1)
- randomise[RANDOM_BODY] = TRUE
- if(S["species_is_always_random"] == 1)
- randomise[RANDOM_SPECIES] = TRUE
- if(S["backbag"])
- S["backbag"] >> backpack
- if(S["hair_style_name"])
- S["hair_style_name"] >> hairstyle
- if(S["facial_style_name"])
- S["facial_style_name"] >> facial_hairstyle
+ return
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
@@ -163,50 +94,51 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
return FALSE
//general preferences
- S["asaycolor"] >> asaycolor
- S["ooccolor"] >> ooccolor
- S["lastchangelog"] >> lastchangelog
- S["UI_style"] >> UI_style
- S["hotkeys"] >> hotkeys
- S["chat_on_map"] >> chat_on_map
- S["max_chat_length"] >> max_chat_length
- S["see_chat_non_mob"] >> see_chat_non_mob
- S["tgui_fancy"] >> tgui_fancy
- S["tgui_lock"] >> tgui_lock
- S["buttons_locked"] >> buttons_locked
- S["windowflash"] >> windowflashing
- S["be_special"] >> be_special
+ READ_FILE(S["asaycolor"], asaycolor)
+ READ_FILE(S["ooccolor"], ooccolor)
+ READ_FILE(S["lastchangelog"], lastchangelog)
+ READ_FILE(S["UI_style"], UI_style)
+ READ_FILE(S["hotkeys"], hotkeys)
+ READ_FILE(S["chat_on_map"], chat_on_map)
+ READ_FILE(S["max_chat_length"], max_chat_length)
+ READ_FILE(S["see_chat_non_mob"] , see_chat_non_mob)
+ READ_FILE(S["see_rc_emotes"] , see_rc_emotes)
+ READ_FILE(S["tgui_fancy"], tgui_fancy)
+ READ_FILE(S["tgui_lock"], tgui_lock)
+ READ_FILE(S["buttons_locked"], buttons_locked)
+ READ_FILE(S["windowflash"], windowflashing)
+ READ_FILE(S["be_special"] , be_special)
- S["default_slot"] >> default_slot
- S["chat_toggles"] >> chat_toggles
- S["toggles"] >> toggles
- S["ghost_form"] >> ghost_form
- S["ghost_orbit"] >> ghost_orbit
- S["ghost_accs"] >> ghost_accs
- S["ghost_others"] >> ghost_others
- S["preferred_map"] >> preferred_map
- S["ignoring"] >> ignoring
- S["ghost_hud"] >> ghost_hud
- S["inquisitive_ghost"] >> inquisitive_ghost
- S["uses_glasses_colour"]>> uses_glasses_colour
- S["clientfps"] >> clientfps
- S["parallax"] >> parallax
- S["ambientocclusion"] >> ambientocclusion
- S["auto_fit_viewport"] >> auto_fit_viewport
- S["widescreenpref"] >> widescreenpref
- S["pixel_size"] >> pixel_size
- S["scaling_method"] >> scaling_method
- S["menuoptions"] >> menuoptions
- S["enable_tips"] >> enable_tips
- S["tip_delay"] >> tip_delay
- S["pda_style"] >> pda_style
- S["pda_color"] >> pda_color
+ READ_FILE(S["default_slot"], default_slot)
+ READ_FILE(S["chat_toggles"], chat_toggles)
+ READ_FILE(S["toggles"], toggles)
+ READ_FILE(S["ghost_form"], ghost_form)
+ READ_FILE(S["ghost_orbit"], ghost_orbit)
+ READ_FILE(S["ghost_accs"], ghost_accs)
+ READ_FILE(S["ghost_others"], ghost_others)
+ READ_FILE(S["preferred_map"], preferred_map)
+ READ_FILE(S["ignoring"], ignoring)
+ READ_FILE(S["ghost_hud"], ghost_hud)
+ READ_FILE(S["inquisitive_ghost"], inquisitive_ghost)
+ READ_FILE(S["uses_glasses_colour"], uses_glasses_colour)
+ READ_FILE(S["clientfps"], clientfps)
+ READ_FILE(S["parallax"], parallax)
+ READ_FILE(S["ambientocclusion"], ambientocclusion)
+ READ_FILE(S["auto_fit_viewport"], auto_fit_viewport)
+ READ_FILE(S["widescreenpref"], widescreenpref)
+ READ_FILE(S["pixel_size"], pixel_size)
+ READ_FILE(S["scaling_method"], scaling_method)
+ READ_FILE(S["menuoptions"], menuoptions)
+ READ_FILE(S["enable_tips"], enable_tips)
+ READ_FILE(S["tip_delay"], tip_delay)
+ READ_FILE(S["pda_style"], pda_style)
+ READ_FILE(S["pda_color"], pda_color)
// Custom hotkeys
- S["key_bindings"] >> key_bindings
+ READ_FILE(S["key_bindings"], key_bindings)
// hearted
- S["hearted_until"] >> hearted_until
+ READ_FILE(S["hearted_until"], hearted_until)
if(hearted_until > world.realtime)
hearted = TRUE
@@ -219,21 +151,22 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
ooccolor = sanitize_ooccolor(sanitize_hexcolor(ooccolor, 6, 1, initial(ooccolor)))
lastchangelog = sanitize_text(lastchangelog, initial(lastchangelog))
UI_style = sanitize_inlist(UI_style, GLOB.available_ui_styles, GLOB.available_ui_styles[1])
- hotkeys = sanitize_integer(hotkeys, 0, 1, initial(hotkeys))
- chat_on_map = sanitize_integer(chat_on_map, 0, 1, initial(chat_on_map))
+ hotkeys = sanitize_integer(hotkeys, FALSE, TRUE, initial(hotkeys))
+ chat_on_map = sanitize_integer(chat_on_map, FALSE, TRUE, initial(chat_on_map))
max_chat_length = sanitize_integer(max_chat_length, 1, CHAT_MESSAGE_MAX_LENGTH, initial(max_chat_length))
- see_chat_non_mob = sanitize_integer(see_chat_non_mob, 0, 1, initial(see_chat_non_mob))
- tgui_fancy = sanitize_integer(tgui_fancy, 0, 1, initial(tgui_fancy))
- tgui_lock = sanitize_integer(tgui_lock, 0, 1, initial(tgui_lock))
- buttons_locked = sanitize_integer(buttons_locked, 0, 1, initial(buttons_locked))
- windowflashing = sanitize_integer(windowflashing, 0, 1, initial(windowflashing))
+ see_chat_non_mob = sanitize_integer(see_chat_non_mob, FALSE, TRUE, initial(see_chat_non_mob))
+ see_rc_emotes = sanitize_integer(see_rc_emotes, FALSE, TRUE, initial(see_rc_emotes))
+ tgui_fancy = sanitize_integer(tgui_fancy, FALSE, TRUE, initial(tgui_fancy))
+ tgui_lock = sanitize_integer(tgui_lock, FALSE, TRUE, initial(tgui_lock))
+ buttons_locked = sanitize_integer(buttons_locked, FALSE, TRUE, initial(buttons_locked))
+ windowflashing = sanitize_integer(windowflashing, FALSE, TRUE, initial(windowflashing))
default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot))
toggles = sanitize_integer(toggles, 0, (2**24)-1, initial(toggles))
clientfps = sanitize_integer(clientfps, 0, 1000, 0)
parallax = sanitize_integer(parallax, PARALLAX_INSANE, PARALLAX_DISABLE, null)
- ambientocclusion = sanitize_integer(ambientocclusion, 0, 1, initial(ambientocclusion))
- auto_fit_viewport = sanitize_integer(auto_fit_viewport, 0, 1, initial(auto_fit_viewport))
- widescreenpref = sanitize_integer(widescreenpref, 0, 1, initial(widescreenpref))
+ ambientocclusion = sanitize_integer(ambientocclusion, FALSE, TRUE, initial(ambientocclusion))
+ auto_fit_viewport = sanitize_integer(auto_fit_viewport, FALSE, TRUE, initial(auto_fit_viewport))
+ widescreenpref = sanitize_integer(widescreenpref, FALSE, TRUE, initial(widescreenpref))
pixel_size = sanitize_integer(pixel_size, PIXEL_SCALING_AUTO, PIXEL_SCALING_3X, initial(pixel_size))
scaling_method = sanitize_text(scaling_method, initial(scaling_method))
ghost_form = sanitize_inlist(ghost_form, GLOB.ghost_forms, initial(ghost_form))
@@ -268,6 +201,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["chat_on_map"], chat_on_map)
WRITE_FILE(S["max_chat_length"], max_chat_length)
WRITE_FILE(S["see_chat_non_mob"], see_chat_non_mob)
+ WRITE_FILE(S["see_rc_emotes"], see_rc_emotes)
WRITE_FILE(S["tgui_fancy"], tgui_fancy)
WRITE_FILE(S["tgui_lock"], tgui_lock)
WRITE_FILE(S["buttons_locked"], buttons_locked)
@@ -324,7 +258,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//Species
var/species_id
- S["species"] >> species_id
+ READ_FILE(S["species"], species_id)
if(species_id)
var/newtype = GLOB.species_list[species_id]
if(newtype)
@@ -339,65 +273,65 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["feature_ethcolor"] , "9c3030")
//Character
- S["real_name"] >> real_name
- S["gender"] >> gender
- S["body_type"] >> body_type
- S["age"] >> age
- S["hair_color"] >> hair_color
- S["facial_hair_color"] >> facial_hair_color
- S["eye_color"] >> eye_color
- S["skin_tone"] >> skin_tone
- S["hairstyle_name"] >> hairstyle
- S["facial_style_name"] >> facial_hairstyle
- S["underwear"] >> underwear
- S["underwear_color"] >> underwear_color
- S["undershirt"] >> undershirt
- S["socks"] >> socks
- S["backpack"] >> backpack
- S["jumpsuit_style"] >> jumpsuit_style
- S["uplink_loc"] >> uplink_spawn_loc
- S["playtime_reward_cloak"] >> playtime_reward_cloak
- S["phobia"] >> phobia
- S["randomise"] >> randomise
- S["feature_mcolor"] >> features["mcolor"]
- S["feature_ethcolor"] >> features["ethcolor"]
- S["feature_lizard_tail"] >> features["tail_lizard"]
- S["feature_lizard_snout"] >> features["snout"]
- S["feature_lizard_horns"] >> features["horns"]
- S["feature_lizard_frills"] >> features["frills"]
- S["feature_lizard_spines"] >> features["spines"]
- S["feature_lizard_body_markings"] >> features["body_markings"]
- S["feature_lizard_legs"] >> features["legs"]
- S["feature_moth_wings"] >> features["moth_wings"]
- S["feature_moth_markings"] >> features["moth_markings"]
- S["persistent_scars"] >> persistent_scars
- S["scars1"] >> scars_list["1"]
- S["scars2"] >> scars_list["2"]
- S["scars3"] >> scars_list["3"]
- S["scars4"] >> scars_list["4"]
- S["scars5"] >> scars_list["5"]
+ READ_FILE(S["real_name"], real_name)
+ READ_FILE(S["gender"], gender)
+ READ_FILE(S["body_type"], body_type)
+ READ_FILE(S["age"], age)
+ READ_FILE(S["hair_color"], hair_color)
+ READ_FILE(S["facial_hair_color"], facial_hair_color)
+ READ_FILE(S["eye_color"], eye_color)
+ READ_FILE(S["skin_tone"], skin_tone)
+ READ_FILE(S["hairstyle_name"], hairstyle)
+ READ_FILE(S["facial_style_name"], facial_hairstyle)
+ READ_FILE(S["underwear"], underwear)
+ READ_FILE(S["underwear_color"], underwear_color)
+ READ_FILE(S["undershirt"], undershirt)
+ READ_FILE(S["socks"], socks)
+ READ_FILE(S["backpack"], backpack)
+ READ_FILE(S["jumpsuit_style"], jumpsuit_style)
+ READ_FILE(S["uplink_loc"], uplink_spawn_loc)
+ READ_FILE(S["playtime_reward_cloak"], playtime_reward_cloak)
+ READ_FILE(S["phobia"], phobia)
+ READ_FILE(S["randomise"], randomise)
+ READ_FILE(S["feature_mcolor"], features["mcolor"])
+ READ_FILE(S["feature_ethcolor"], features["ethcolor"])
+ READ_FILE(S["feature_lizard_tail"], features["tail_lizard"])
+ READ_FILE(S["feature_lizard_snout"], features["snout"])
+ READ_FILE(S["feature_lizard_horns"], features["horns"])
+ READ_FILE(S["feature_lizard_frills"], features["frills"])
+ READ_FILE(S["feature_lizard_spines"], features["spines"])
+ READ_FILE(S["feature_lizard_body_markings"], features["body_markings"])
+ READ_FILE(S["feature_lizard_legs"], features["legs"])
+ READ_FILE(S["feature_moth_wings"], features["moth_wings"])
+ READ_FILE(S["feature_moth_markings"], features["moth_markings"])
+ READ_FILE(S["persistent_scars"] , persistent_scars)
+ READ_FILE(S["scars1"], scars_list["1"])
+ READ_FILE(S["scars2"], scars_list["2"])
+ READ_FILE(S["scars3"], scars_list["3"])
+ READ_FILE(S["scars4"], scars_list["4"])
+ READ_FILE(S["scars5"], scars_list["5"])
if(!CONFIG_GET(flag/join_with_mutant_humans))
features["tail_human"] = "none"
features["ears"] = "none"
else
- S["feature_human_tail"] >> features["tail_human"]
- S["feature_human_ears"] >> features["ears"]
+ READ_FILE(S["feature_human_tail"], features["tail_human"])
+ READ_FILE(S["feature_human_ears"], features["ears"])
//Custom names
for(var/custom_name_id in GLOB.preferences_custom_names)
var/savefile_slot_name = custom_name_id + "_name" //TODO remove this
- S[savefile_slot_name] >> custom_names[custom_name_id]
+ READ_FILE(S[savefile_slot_name], custom_names[custom_name_id])
- S["preferred_ai_core_display"] >> preferred_ai_core_display
- S["prefered_security_department"] >> prefered_security_department
+ READ_FILE(S["preferred_ai_core_display"], preferred_ai_core_display)
+ READ_FILE(S["prefered_security_department"], prefered_security_department)
//Jobs
- S["joblessrole"] >> joblessrole
+ READ_FILE(S["joblessrole"], joblessrole)
//Load prefs
- S["job_preferences"] >> job_preferences
+ READ_FILE(S["job_preferences"], job_preferences)
//Quirks
- S["all_quirks"] >> all_quirks
+ READ_FILE(S["all_quirks"], all_quirks)
//try to fix any outdated data if necessary
if(needs_update >= 0)
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 6b8f78f823c..bcab6bc3bb5 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -93,21 +93,28 @@
return ..()
/obj/item/clothing/attackby(obj/item/W, mob/user, params)
- if(damaged_clothes && istype(W, repairable_by))
- var/obj/item/stack/S = W
- switch(damaged_clothes)
- if(CLOTHING_DAMAGED)
- S.use(1)
- repair(user, params)
- if(CLOTHING_SHREDDED)
- if(S.amount < 3)
- to_chat(user, "You require 3 [S.name] to repair [src].")
- return
- to_chat(user, "You begin fixing the damage to [src] with [S]...")
- if(do_after(user, 6 SECONDS, TRUE, src))
- if(S.use(3))
- repair(user, params)
- return 1
+ if(!istype(W, repairable_by))
+ return ..()
+
+ switch(damaged_clothes)
+ if(CLOTHING_PRISTINE)
+ return..()
+ if(CLOTHING_DAMAGED)
+ var/obj/item/stack/cloth_repair = W
+ cloth_repair.use(1)
+ repair(user, params)
+ return TRUE
+ if(CLOTHING_SHREDDED)
+ var/obj/item/stack/cloth_repair = W
+ if(cloth_repair.amount < 3)
+ to_chat(user, "You require 3 [cloth_repair.name] to repair [src].")
+ return TRUE
+ to_chat(user, "You begin fixing the damage to [src] with [cloth_repair]...")
+ if(!do_after(user, 6 SECONDS, TRUE, src) || !cloth_repair.use(3))
+ return TRUE
+ repair(user, params)
+ return TRUE
+
return ..()
/// Set the clothing's integrity back to 100%, remove all damage to bodyparts, and generally fix it up
@@ -354,8 +361,8 @@
to_chat(M, "[src] start[p_s()] to fall apart!")
//This mostly exists so subtypes can call appriopriate update icon calls on the wearer.
-/obj/item/clothing/proc/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED)
- damaged_clothes = damaged_state
+/obj/item/clothing/proc/update_clothes_damaged_state()
+ return
/obj/item/clothing/update_overlays()
. = ..()
diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm
index 32a8d00c748..912a8b6f331 100644
--- a/code/modules/clothing/glasses/_glasses.dm
+++ b/code/modules/clothing/glasses/_glasses.dm
@@ -88,7 +88,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/clothing/glasses/science
name = "science goggles"
@@ -156,7 +156,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
glass_colour_type = /datum/client_colour/glass_colour/lightgreen
/obj/item/clothing/glasses/regular
@@ -228,7 +228,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/clothing/glasses/sunglasses/garb/supergarb
name = "black giga gar glasses"
@@ -248,7 +248,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
glass_colour_type = /datum/client_colour/glass_colour/orange
/obj/item/clothing/glasses/sunglasses/gar/supergar
diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm
index 6a86dd4aafe..316d29cdc02 100644
--- a/code/modules/clothing/glasses/hud.dm
+++ b/code/modules/clothing/glasses/hud.dm
@@ -159,7 +159,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars
name = "giga HUD gar glasses"
diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm
index 104a0172d1a..efb090c1490 100644
--- a/code/modules/clothing/gloves/_gloves.dm
+++ b/code/modules/clothing/gloves/_gloves.dm
@@ -11,14 +11,11 @@
strip_delay = 20
equip_delay_other = 40
-/obj/item/clothing/gloves/ComponentInitialize()
+/obj/item/clothing/gloves/wash(clean_types)
. = ..()
- RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood)
-
-/obj/item/clothing/gloves/proc/clean_blood(datum/source, strength)
- if(strength < CLEAN_STRENGTH_BLOOD)
- return
- transfer_blood = 0
+ if((clean_types & CLEAN_TYPE_BLOOD) && transfer_blood > 0)
+ transfer_blood = 0
+ return TRUE
/obj/item/clothing/gloves/suicide_act(mob/living/carbon/user)
user.visible_message("\the [src] are forcing [user]'s hands around [user.p_their()] neck! It looks like the gloves are possessed!")
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 6ae00237f20..ef71a981b65 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -25,10 +25,6 @@
///any alerts we have active
var/obj/screen/alert/our_alert
-/obj/item/clothing/shoes/ComponentInitialize()
- . = ..()
- RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood)
-
/obj/item/clothing/shoes/suicide_act(mob/living/carbon/user)
if(rand(2)>1)
user.visible_message("[user] begins tying \the [src] up waaay too tightly! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -100,14 +96,16 @@
var/mob/M = loc
M.update_inv_shoes()
-/obj/item/clothing/shoes/proc/clean_blood(datum/source, strength)
- if(strength < CLEAN_STRENGTH_BLOOD)
+/obj/item/clothing/shoes/wash(clean_types)
+ . = ..()
+ if(!(clean_types & CLEAN_TYPE_BLOOD) || blood_state == BLOOD_STATE_NOT_BLOODY)
return
bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0)
blood_state = BLOOD_STATE_NOT_BLOODY
if(ismob(loc))
var/mob/M = loc
M.update_inv_shoes()
+ return TRUE
/obj/item/proc/negates_gravity()
return FALSE
diff --git a/code/modules/clothing/spacesuits/chronosuit.dm b/code/modules/clothing/spacesuits/chronosuit.dm
index c9e7e0f5a09..9dfb52c557b 100644
--- a/code/modules/clothing/spacesuits/chronosuit.dm
+++ b/code/modules/clothing/spacesuits/chronosuit.dm
@@ -95,7 +95,7 @@
user.update_atom_colour()
user.animate_movement = FORWARD_STEPS
user.notransform = 0
- user.anchored = FALSE
+ user.set_anchored(FALSE)
teleporting = 0
for(var/obj/item/I in user.held_items)
REMOVE_TRAIT(I, TRAIT_NODROP, CHRONOSUIT_TRAIT)
@@ -137,7 +137,7 @@
user.animate_movement = NO_STEPS
user.changeNext_move(8 + phase_in_ds)
user.notransform = 1
- user.anchored = TRUE
+ user.set_anchored(TRUE)
user.Stun(INFINITY)
animate(user, color = "#00ccee", time = 3)
diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm
index 29f64924c0e..0a08186cc83 100644
--- a/code/modules/clothing/spacesuits/plasmamen.dm
+++ b/code/modules/clothing/spacesuits/plasmamen.dm
@@ -77,13 +77,6 @@
playsound(src, 'sound/mecha/mechmove03.ogg', 50, TRUE) //Visors don't just come from nothing
update_icon()
-/obj/item/clothing/head/helmet/space/plasmaman/worn_overlays(isinhands)
- . = ..()
- if(!isinhands && !up)
- . += mutable_appearance('icons/mob/clothing/head.dmi', visor_icon)
- else
- cut_overlays()
-
/obj/item/clothing/head/helmet/space/plasmaman/update_overlays()
. = ..()
. += visor_icon
@@ -99,8 +92,7 @@
smile_color = CR.paint_color
to_chat(user, "You draw a smiley on the helmet visor.")
update_icon()
- return
- if(smile == TRUE)
+ else
to_chat(user, "Seems like someone already drew something on this helmet's visor!")
/obj/item/clothing/head/helmet/space/plasmaman/worn_overlays(isinhands)
@@ -114,15 +106,12 @@
else
cut_overlays()
-/obj/item/clothing/head/helmet/space/plasmaman/ComponentInitialize()
+/obj/item/clothing/head/helmet/space/plasmaman/wash(clean_types)
. = ..()
- RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/wipe_that_smile_off_your_face)
-
-///gets called when receiving the CLEAN_ACT signal from something, i.e soap or a shower. exists to remove any smiley faces drawn on the helmet.
-/obj/item/clothing/head/helmet/space/plasmaman/proc/wipe_that_smile_off_your_face()
- if(smile)
+ if(smile && (clean_types & CLEAN_TYPE_PAINT))
smile = FALSE
- cut_overlays()
+ update_icon()
+ return TRUE
/obj/item/clothing/head/helmet/space/plasmaman/attack_self(mob/user)
on = !on
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index 78514228098..35fb84a5745 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -9,7 +9,7 @@
equip_delay_other = 40
max_integrity = 250
resistance_flags = NONE
- armor = list("melee" = 35, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "wound" = 15)
+ armor = list("melee" = 35, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "wound" = 10)
/obj/item/clothing/suit/armor/Initialize()
. = ..()
@@ -49,7 +49,7 @@
icon_state = "hos"
inhand_icon_state = "greatcoat"
body_parts_covered = CHEST|GROIN|ARMS|LEGS
- armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 90, "wound" = 20)
+ armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 90, "wound" = 10)
cold_protection = CHEST|GROIN|LEGS|ARMS
heat_protection = CHEST|GROIN|LEGS|ARMS
strip_delay = 80
@@ -118,7 +118,7 @@
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
- armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "wound" = 30)
+ armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "wound" = 20)
clothing_flags = BLOCKS_SHOVE_KNOCKDOWN
strip_delay = 80
equip_delay_other = 60
diff --git a/code/modules/clothing/under/color.dm b/code/modules/clothing/under/color.dm
index b47b6ee0c59..87b1b2b9866 100644
--- a/code/modules/clothing/under/color.dm
+++ b/code/modules/clothing/under/color.dm
@@ -14,7 +14,7 @@
/obj/item/clothing/under/color/random/Initialize()
..()
- var/obj/item/clothing/under/color/C = pick(subtypesof(/obj/item/clothing/under/color) - typesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/random - /obj/item/clothing/under/color/grey/glorf - /obj/item/clothing/under/color/black/ghost)
+ var/obj/item/clothing/under/color/C = pick(subtypesof(/obj/item/clothing/under/color) - typesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/random - /obj/item/clothing/under/color/grey/ancient - /obj/item/clothing/under/color/black/ghost)
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
H.equip_to_slot_or_del(new C(H), ITEM_SLOT_ICLOTHING) //or else you end up with naked assistants running around everywhere...
@@ -65,14 +65,10 @@
icon_state = "grey_skirt"
inhand_icon_state = "gy_suit"
-/obj/item/clothing/under/color/grey/glorf
+/obj/item/clothing/under/color/grey/ancient
name = "ancient jumpsuit"
desc = "A terribly ragged and frayed grey jumpsuit. It looks like it hasn't been washed in over a decade."
-/obj/item/clothing/under/color/grey/glorf/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
- owner.forcesay(GLOB.hit_appends)
- return 0
-
/obj/item/clothing/under/color/blue
name = "blue jumpsuit"
icon_state = "blue"
diff --git a/code/modules/detectivework/detective_work.dm b/code/modules/detectivework/detective_work.dm
index fe519703671..98df8ad72d9 100644
--- a/code/modules/detectivework/detective_work.dm
+++ b/code/modules/detectivework/detective_work.dm
@@ -46,10 +46,10 @@
if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects
if(add_blood_DNA(G.return_blood_DNA()) && length(G.return_blood_DNA()) > old) //only reduces the bloodiness of our gloves if the item wasn't already bloody
G.transfer_blood--
- else if(M.bloody_hands > 1)
+ else if(M.blood_in_hands > 1)
old = length(M.return_blood_DNA())
if(add_blood_DNA(M.return_blood_DNA()) && length(M.return_blood_DNA()) > old)
- M.bloody_hands--
+ M.blood_in_hands--
var/datum/component/forensics/D = AddComponent(/datum/component/forensics)
. = D.add_fibers(M)
@@ -92,7 +92,7 @@
G.add_blood_DNA(blood_dna)
else if(length(blood_dna))
AddComponent(/datum/component/forensics, null, null, blood_dna)
- bloody_hands = rand(2, 4)
+ blood_in_hands = rand(2, 4)
update_inv_gloves() //handles bloody hands overlays and updating
return TRUE
diff --git a/code/modules/detectivework/evidence.dm b/code/modules/detectivework/evidence.dm
index ead5bd6309a..1e0c5df0c3d 100644
--- a/code/modules/detectivework/evidence.dm
+++ b/code/modules/detectivework/evidence.dm
@@ -25,7 +25,7 @@
desc = initial(desc)
/obj/item/evidencebag/proc/evidencebagEquip(obj/item/I, mob/user)
- if(!istype(I) || I.anchored == 1)
+ if(!istype(I) || I.anchored)
return
if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE) && SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE))
diff --git a/code/modules/detectivework/footprints_and_rag.dm b/code/modules/detectivework/footprints_and_rag.dm
index e7e61f3e39b..aaaa1ad1bff 100644
--- a/code/modules/detectivework/footprints_and_rag.dm
+++ b/code/modules/detectivework/footprints_and_rag.dm
@@ -42,4 +42,4 @@
user.visible_message("[user] starts to wipe down [A] with [src]!", "You start to wipe down [A] with [src]...")
if(do_after(user,30, target = A))
user.visible_message("[user] finishes wiping off [A]!", "You finish wiping off [A].")
- SEND_SIGNAL(A, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM)
+ A.wash(CLEAN_SCRUB)
diff --git a/code/modules/economy/account.dm b/code/modules/economy/account.dm
index a16c12eec25..3fc9115f403 100644
--- a/code/modules/economy/account.dm
+++ b/code/modules/economy/account.dm
@@ -9,6 +9,8 @@
var/add_to_accounts = TRUE
var/account_id
var/being_dumped = FALSE //pink levels are rising
+ var/datum/bounty/civilian_bounty
+ var/bounty_timer = 0
/datum/bank_account/New(newname, job, modifier = 1)
if(add_to_accounts)
@@ -54,6 +56,7 @@
if(free)
adjust_money(money_to_transfer)
SSblackbox.record_feedback("amount", "free_income", money_to_transfer)
+ SSeconomy.station_target += money_to_transfer
log_econ("[money_to_transfer] credits were given to [src.account_holder]'s account from income.")
else
var/datum/bank_account/D = SSeconomy.get_dep_account(account_job.paycheck_department)
@@ -103,6 +106,47 @@
M.playsound_local(get_turf(sound_atom), 'sound/machines/twobeep_high.ogg', 50, TRUE)
to_chat(M, "[icon2html(icon_source, M)] [message]")
+/**
+ * Returns a string with the civilian bounty's description on it.
+ */
+/datum/bank_account/proc/bounty_text()
+ if(!civilian_bounty)
+ return FALSE
+ if(istype(civilian_bounty, /datum/bounty/item))
+ var/datum/bounty/item/item = civilian_bounty
+ return item.description
+ if(istype(civilian_bounty, /datum/bounty/reagent))
+ var/datum/bounty/reagent/chemical = civilian_bounty
+ return chemical.description
+
+/**
+ * Returns the required item count, or required chemical units required to submit a bounty.
+ */
+/datum/bank_account/proc/bounty_num()
+ if(!civilian_bounty)
+ return FALSE
+ if(istype(civilian_bounty, /datum/bounty/item))
+ var/datum/bounty/item/item = civilian_bounty
+ return "[item.shipped_count]/[item.required_count]"
+ if(istype(civilian_bounty, /datum/bounty/reagent))
+ var/datum/bounty/reagent/chemical = civilian_bounty
+ return "[chemical.shipped_volume]/[chemical.required_volume] u"
+
+/**
+ * Produces the value of the account's civilian bounty reward, if able.
+ */
+/datum/bank_account/proc/bounty_value()
+ if(!civilian_bounty)
+ return FALSE
+ return civilian_bounty.reward
+
+/**
+ * Performs house-cleaning on variables when a civilian bounty is replaced, or, when a bounty is claimed.
+ */
+/datum/bank_account/proc/reset_bounty()
+ civilian_bounty = null
+ bounty_timer = 0
+
/datum/bank_account/department
account_holder = "Guild Credit Agency"
var/department_id = "REPLACE_ME"
diff --git a/code/modules/events/market_crash.dm b/code/modules/events/market_crash.dm
new file mode 100644
index 00000000000..18548e27504
--- /dev/null
+++ b/code/modules/events/market_crash.dm
@@ -0,0 +1,43 @@
+/**
+ * An event which decreases the station target temporarily, causing the inflation var to increase heavily.
+ *
+ * Done by decreasing the station_target by a high value per crew member, resulting in the station total being much higher than the target, and causing artificial inflation.
+ */
+/datum/round_event_control/market_crash
+ name = "Market Crash"
+ typepath = /datum/round_event/market_crash
+ weight = 10
+
+/datum/round_event/market_crash
+ var/market_dip = 0
+
+/datum/round_event/market_crash/setup()
+ startWhen = 1
+ endWhen = rand(25, 50)
+ announceWhen = 2
+
+/datum/round_event/market_crash/announce(fake)
+ var/list/poss_reasons = list("the alignment of the moon and the sun",\
+ "some risky housing market outcomes",\
+ "The B.E.P.I.S. team's untimely downfall",\
+ "speculative Terragov grants backfiring",\
+ "greatly exaggerated reports of Nanotrasen accountancy personnel committing mass suicide")
+ var/reason = pick(poss_reasons)
+ priority_announce("Due to [reason], prices for on-station vendors will be increased for a short period.", "Nanotrasen Accounting Division")
+
+///This does not work and I could use some help morking this one out further.
+/datum/round_event/market_crash/start()
+ . = ..()
+ var/num_accounts = 0
+ for(var/A in SSeconomy.bank_accounts)
+ num_accounts += 1
+ market_dip = rand(1000,10000) * num_accounts
+ SSeconomy.station_target -= market_dip
+ SSeconomy.station_target = max(SSeconomy.station_target, 1)
+ SSeconomy.price_update()
+
+/datum/round_event/market_crash/end()
+ . = ..()
+ SSeconomy.station_target += market_dip
+ SSeconomy.price_update()
+ priority_announce("Prices for on-station vendors have now stabilized.", "Nanotrasen Accounting Division")
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index f66f33a121c..eaf85ac8db5 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -365,6 +365,7 @@
for(var/datum/export/E in ex.total_amount)
total_report.total_amount[E] += ex.total_amount[E]
total_report.total_value[E] += ex.total_value[E]
+ playsound(loc, 'sound/machines/wewewew.ogg', 70, TRUE)
points += value
@@ -380,16 +381,18 @@
if(sending)
return
sending = TRUE
- status_report = "Sending..."
+ status_report = "Sending... "
pad.visible_message("[pad] starts charging up.")
pad.icon_state = pad.warmup_state
sending_timer = addtimer(CALLBACK(src,.proc/send),warmup_time, TIMER_STOPPABLE)
-/obj/machinery/computer/piratepad_control/proc/stop_sending()
+/obj/machinery/computer/piratepad_control/proc/stop_sending(custom_report)
if(!sending)
return
sending = FALSE
status_report = "Ready for delivery."
+ if(custom_report)
+ status_report = custom_report
pad.icon_state = pad.idle_state
deltimer(sending_timer)
diff --git a/code/modules/events/shuttle_loan.dm b/code/modules/events/shuttle_loan.dm
index e24eac6d922..bf68ea98ce0 100644
--- a/code/modules/events/shuttle_loan.dm
+++ b/code/modules/events/shuttle_loan.dm
@@ -260,7 +260,7 @@
/obj/machinery/syndicatebomb/shuttle_loan/Initialize()
. = ..()
- setAnchored(TRUE)
+ set_anchored(TRUE)
timer_set = rand(480, 600) //once the supply shuttle docks (after 5 minutes travel time), players have between 3-5 minutes to defuse the bomb
activate()
update_icon()
diff --git a/code/modules/events/swarmer.dm b/code/modules/events/swarmer.dm
new file mode 100644
index 00000000000..ffb79a43e72
--- /dev/null
+++ b/code/modules/events/swarmer.dm
@@ -0,0 +1,27 @@
+/datum/round_event_control/spawn_swarmer
+ name = "Spawn Swarmer Beacon"
+ typepath = /datum/round_event/spawn_swarmer
+ weight = 8
+ max_occurrences = 1 //Only once okay fam
+ earliest_start = 30 MINUTES
+ min_players = 15
+
+/datum/round_event/spawn_swarmer/announce(fake)
+ priority_announce("Our long-range sensors have detected that your station's defenses have been breached by some sort of alien device. We suggest searching for and destroying it as soon as possible.", "[command_name()] High-Priority Update")
+
+/datum/round_event/spawn_swarmer
+ announceWhen = 70
+
+/datum/round_event/spawn_swarmer/start()
+ var/list/spawn_locs = list()
+ for(var/x in GLOB.xeno_spawn)
+ var/turf/spawn_turf = x
+ var/light_amount = spawn_turf.get_lumcount()
+ if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD)
+ spawn_locs += spawn_turf
+ if(!spawn_locs.len)
+ message_admins("No valid spawn locations found in GLOB.xeno_spawn, aborting swarmer spawning...")
+ return MAP_ERROR
+ var/obj/structure/swarmer_beacon/new_beacon = new /obj/structure/swarmer_beacon(pick(spawn_locs))
+ log_game("A Swarmer Beacon was spawned via an event.")
+ notify_ghosts("\A Swarmer Beacon has spawned!", source = new_beacon, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "Swarmer Beacon Spawned")
diff --git a/code/modules/events/wizard/rpgloot.dm b/code/modules/events/wizard/rpgloot.dm
index b2719c03a78..45b8e7fc94b 100644
--- a/code/modules/events/wizard/rpgloot.dm
+++ b/code/modules/events/wizard/rpgloot.dm
@@ -34,6 +34,7 @@
desc = "Somehow, this piece of paper can be applied to items to make them \"better\". Apparently there's a risk of losing the item if it's already \"too good\". This all feels so arbitrary..."
icon = 'icons/obj/wizard.dmi'
icon_state = "scroll"
+ worn_icon_state = "scroll"
w_class = WEIGHT_CLASS_TINY
var/upgrade_amount = 1
diff --git a/code/modules/fields/fields.dm b/code/modules/fields/fields.dm
index 3e401051499..e8e677e4d68 100644
--- a/code/modules/fields/fields.dm
+++ b/code/modules/fields/fields.dm
@@ -78,7 +78,7 @@
/datum/proximity_monitor/advanced/proc/process_edge_turf(turf/T)
-/datum/proximity_monitor/advanced/New()
+/datum/proximity_monitor/advanced/New(atom/_host, range, _ignore_if_not_on_turf = TRUE)
if(requires_processing)
START_PROCESSING(SSfields, src)
diff --git a/code/modules/fields/peaceborg_dampener.dm b/code/modules/fields/peaceborg_dampener.dm
index 5a1f1491648..c743270d2f1 100644
--- a/code/modules/fields/peaceborg_dampener.dm
+++ b/code/modules/fields/peaceborg_dampener.dm
@@ -21,10 +21,10 @@
var/list/obj/projectile/staging
use_host_turf = TRUE
-/datum/proximity_monitor/advanced/peaceborg_dampener/New()
+/datum/proximity_monitor/advanced/peaceborg_dampener/New(atom/_host, range, _ignore_if_not_on_turf = TRUE)
tracked = list()
staging = list()
- ..()
+ return ..()
/datum/proximity_monitor/advanced/peaceborg_dampener/Destroy()
return ..()
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index 0adef27f864..89611b8dd57 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -723,7 +723,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
var/image/speech_overlay = image('icons/mob/talk.dmi', person, "default0", layer = ABOVE_MOB_LAYER)
INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, speech_overlay, list(target.client), 30)
if (target.client?.prefs.chat_on_map)
- target.create_chat_message(person, understood_language, chosen, spans, 0)
+ target.create_chat_message(person, understood_language, chosen, spans)
to_chat(target, message)
qdel(src)
diff --git a/code/modules/food_and_drinks/drinks/drinks.dm b/code/modules/food_and_drinks/drinks/drinks.dm
index 8bbd468a7f5..8b389098f17 100644
--- a/code/modules/food_and_drinks/drinks/drinks.dm
+++ b/code/modules/food_and_drinks/drinks/drinks.dm
@@ -44,14 +44,33 @@
M.visible_message("[user] fed [M] the contents of [src].", \
"[user] fed you the contents of [src].")
log_combat(user, M, "fed", reagents.log_list())
-
+ SEND_SIGNAL(src, COMSIG_DRINK_DRANK, M, user)
var/fraction = min(gulp_size/reagents.total_volume, 1)
checkLiked(fraction, M)
reagents.expose(M, INGEST, fraction)
reagents.trans_to(M, gulp_size, transfered_by = user)
playsound(M.loc,'sound/items/drink.ogg', rand(10,50), TRUE)
+ if(iscarbon(M))
+ var/mob/living/carbon/carbon_drinker = M
+ var/list/diseases = carbon_drinker.get_static_viruses()
+ if(LAZYLEN(diseases))
+ var/list/datum/disease/diseases_to_add = list()
+ for(var/d in diseases)
+ var/datum/disease/malady = d
+ if(malady.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS)
+ diseases_to_add += malady
+ if(LAZYLEN(diseases_to_add))
+ AddComponent(/datum/component/infective, diseases_to_add)
return TRUE
+/*
+ * On accidental consumption, make sure the container is partially glass, and continue to the reagent_container proc
+ */
+/obj/item/reagent_containers/food/drinks/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ if(isGlass && !custom_materials)
+ custom_materials = list(SSmaterials.GetMaterialRef(/datum/material/glass) = 5) //sets it to glass so, later on, it gets picked up by the glass catch
+ return ..()
+
/obj/item/reagent_containers/food/drinks/afterattack(obj/target, mob/user , proximity)
. = ..()
if(!proximity)
@@ -377,7 +396,8 @@
custom_materials = list(/datum/material/plastic=3000)
list_reagents = list(/datum/reagent/water = 100)
volume = 100
- amount_per_transfer_from_this = 20
+ amount_per_transfer_from_this = 10
+ possible_transfer_amounts = list(5,10,15,20,25,30,50,100)
cap_icon_state = "bottle_cap"
/obj/item/reagent_containers/food/drinks/waterbottle/large/empty
diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
index 8f2c39a4754..ee6bd312474 100644
--- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm
+++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
@@ -146,7 +146,7 @@
inhand_icon_state = "beer"
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("stabbed", "slashed", "attacked")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
var/static/icon/broken_outline = icon('icons/obj/drinks.dmi', "broken")
/obj/item/broken_bottle/Initialize()
@@ -262,6 +262,27 @@
list_reagents = list(/datum/reagent/consumable/ethanol/wine = 100)
foodtype = FRUIT | ALCOHOL
+/obj/item/reagent_containers/food/drinks/bottle/wine/add_initial_reagents()
+ . = ..()
+ var/wine_info = generate_vintage()
+ var/datum/reagent/consumable/ethanol/wine/W = locate() in reagents.reagent_list
+ if(W)
+ LAZYSET(W.data,"vintage",wine_info)
+
+/obj/item/reagent_containers/food/drinks/bottle/wine/proc/generate_vintage()
+ return "[GLOB.year_integer + 540] Nanotrasen Light Red"
+
+/obj/item/reagent_containers/food/drinks/bottle/wine/unlabeled
+ name = "unlabeled wine bottle"
+ desc = "There's no label on this wine bottle."
+
+/obj/item/reagent_containers/food/drinks/bottle/wine/unlabeled/generate_vintage()
+ var/current_year = GLOB.year_integer + 540
+ var/year = rand(current_year-50,current_year)
+ var/type = pick("Sparkling","Dry White","Sweet White","Rich White","Rose","Light Red","Medium Red","Bold Red","Dessert")
+ var/origin = pick("Nanotrasen","Syndicate","Local")
+ return "[year] [origin] [type]"
+
/obj/item/reagent_containers/food/drinks/bottle/absinthe
name = "extra-strong absinthe"
desc = "A strong alcoholic drink brewed and distributed by"
diff --git a/code/modules/food_and_drinks/food/snacks.dm b/code/modules/food_and_drinks/food/snacks.dm
index 023adeb3932..f5fc99fbc52 100644
--- a/code/modules/food_and_drinks/food/snacks.dm
+++ b/code/modules/food_and_drinks/food/snacks.dm
@@ -80,10 +80,13 @@ All foods are distributed among various categories. Use common sense.
if(istype(location))
location.put_in_hands(trash_item)
+//it's never an accident when you eat food! right?
+/obj/item/reagent_containers/food/snacks/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ return TRUE
+
/obj/item/reagent_containers/food/snacks/attack_self(mob/user)
return
-
/obj/item/reagent_containers/food/snacks/attack(mob/living/M, mob/living/user, def_zone)
if(user.a_intent == INTENT_HARM)
return ..()
@@ -185,10 +188,9 @@ All foods are distributed among various categories. Use common sense.
var/obj/item/reagent_containers/food/snacks/customizable/C = new custom_food_type(get_turf(src))
C.initialize_custom_food(src, S, user)
return 0
- var/sharp = W.get_sharpness()
- if(sharp)
- if(slice(sharp, W, user))
- return 1
+ if(user.a_intent != INTENT_DISARM)
+ var/sharp = W.get_sharpness()
+ return sharp && slice(sharp, W, user)
else
..()
@@ -228,21 +230,10 @@ All foods are distributed among various categories. Use common sense.
to_chat(user, "You cannot slice [src] here! You need a table or at least a tray.")
return FALSE
- var/slices_lost = 0
- if (accuracy >= IS_SHARP_ACCURATE)
- user.visible_message( \
- "[user] slices [src].", \
- "You slice [src]." \
- )
- else
- user.visible_message( \
- "[user] inaccurately slices [src] with [W]!", \
- "You inaccurately slice [src] with your [W]!" \
- )
- slices_lost = rand(1,min(1,round(slices_num/2)))
+ user.visible_message("[user] slices [src].", "You slice [src].")
var/reagents_per_slice = reagents.total_volume/slices_num
- for(var/i=1 to (slices_num-slices_lost))
+ for(var/i in 1 to slices_num)
var/obj/item/reagent_containers/food/snacks/slice = new slice_path (loc)
initialize_slice(slice, reagents_per_slice)
qdel(src)
@@ -259,17 +250,19 @@ All foods are distributed among various categories. Use common sense.
slice.foodtype = foodtype //if something happens that overrode our food type, make sure the slice carries that over
/obj/item/reagent_containers/food/snacks/proc/generate_trash(atom/location)
- if(trash)
- if(ispath(trash, /obj/item))
- . = new trash(location)
- trash = null
- return
- else if(isitem(trash))
- var/obj/item/trash_item = trash
- trash_item.forceMove(location)
- . = trash
- trash = null
- return
+ if(!trash)
+ return
+
+ if(ispath(trash, /obj/item))
+ . = new trash(location)
+ trash = null
+ return
+ else if(isitem(trash))
+ var/obj/item/trash_item = trash
+ trash_item.forceMove(location)
+ . = trash
+ trash = null
+ return
/obj/item/reagent_containers/food/snacks/proc/update_snack_overlays(obj/item/reagent_containers/food/snacks/S)
cut_overlays()
@@ -338,26 +331,78 @@ All foods are distributed among various categories. Use common sense.
/// All the food items that can store an item inside itself, like bread or cake.
/obj/item/reagent_containers/food/snacks/store
w_class = WEIGHT_CLASS_NORMAL
- var/stored_item = 0
+ /// If an item has been stored in the food
+ var/stored_item = FALSE
+ /// The amount of volume the food has on creation
+ var/volume_on_creation = 0
+ /// Allows someone to bypass the small weight class requirement, so they can put whatever they want into a slice of bread
+ var/bypass_weight_limit = FALSE
+
+/obj/item/reagent_containers/food/snacks/store/Initialize()
+ . = ..()
+ if(reagents?.total_volume)
+ volume_on_creation = reagents.total_volume
/obj/item/reagent_containers/food/snacks/store/attackby(obj/item/W, mob/user, params)
..()
- if(W.w_class <= WEIGHT_CLASS_SMALL & !istype(W, /obj/item/reagent_containers/food/snacks)) //can't slip snacks inside, they're used for custom foods.
- if(W.get_sharpness())
- return 0
+ if(istype(W, /obj/item/reagent_containers/food/snacks)) //can't slip snacks inside, they're used for custom foods.
+ return FALSE
+
+ if((bypass_weight_limit || W.w_class <= WEIGHT_CLASS_SMALL))
+ if(W.get_sharpness() && user.a_intent != INTENT_DISARM)
+ return FALSE
+ if(istype(W, /obj/item/storage))
+ return FALSE
if(stored_item)
- return 0
+ return FALSE
if(!iscarbon(user))
- return 0
+ return FALSE
if(contents.len >= 20)
to_chat(user, "[src] is full.")
- return 0
+ return FALSE
+ user.visible_message("[user.name] begins inserting [W] into [src].", \
+ "You start to insert the [W] into \the [src].")
+ if(!do_after(user, 1.5 SECONDS, target = src))
+ return FALSE
to_chat(user, "You slip [W] inside [src].")
user.transferItemToLoc(W, src)
+ log_message("[key_name(user)] inserted [W.name] into [src.name] at [AREACOORD(src)]", LOG_ATTACK)
add_fingerprint(user)
contents += W
- stored_item = 1
- return 1 // no afterattack here
+ stored_item = TRUE
+ return TRUE // no afterattack here
+
+/obj/item/reagent_containers/food/snacks/store/attack(mob/living/carbon/M, mob/living/carbon/user, def_zone)
+ if(!..())
+ return
+ /// What are the odds we eat glass? - [Bitecount / Max number of bites] * 100
+ var/bad_chance_of_discovery = (bitecount / (volume_on_creation / bitesize))*100 //the closer you get to finishing it, the higher the chance you bite into it
+ /// What are the odds we see the glass but don't bite it? - ([Bitecount / Max number of bites] * 100) - 50
+ var/good_chance_of_discovery = bad_chance_of_discovery - 50 //the closer you get to finishing it, the more likely you can see what is in it
+ /// We've found the item, and plan on remove it
+ var/discovered = FALSE
+
+ if(stored_item)
+ for(var/obj/item/I in contents)
+ if(istype(I, /obj/item/reagent_containers/food/snacks))
+ return FALSE
+ if(prob(good_chance_of_discovery))
+ discovered = TRUE
+ to_chat(M, "It feels like there's something in this [src.name]...!")
+
+ else if(prob(bad_chance_of_discovery))
+ log_message("[key_name(user)] just fed [key_name(M)] a/an [I.name] which was hidden in [src.name] at [AREACOORD(src)]", LOG_ATTACK)
+ discovered = I.on_accidental_consumption(M, user, src)
+
+ if(!QDELETED(I) && discovered)
+ contents -= I
+ stored_item = FALSE
+ if(M.put_in_hands(I)) //the moment when you slowly pull out whatever you just bit into in your food
+ to_chat(M, "You slowly pull [I] out of \the [src].")
+ else
+ to_chat(M, "[I] falls out of \the [src].")
+
+ return FALSE
/obj/item/reagent_containers/food/snacks/MouseDrop(atom/over)
var/turf/T = get_turf(src)
@@ -366,4 +411,3 @@ All foods are distributed among various categories. Use common sense.
TB.MouseDrop(over)
else
return ..()
-
diff --git a/code/modules/food_and_drinks/food/snacks_other.dm b/code/modules/food_and_drinks/food/snacks_other.dm
index 24904fdad77..06b6bc30e14 100644
--- a/code/modules/food_and_drinks/food/snacks_other.dm
+++ b/code/modules/food_and_drinks/food/snacks_other.dm
@@ -814,3 +814,17 @@
tastes = list("fried corn" = 1)
foodtype = JUNKFOOD | FRIED
value = FOOD_JUNK
+
+/obj/item/reagent_containers/food/snacks/cornchips/Crossed(atom/movable/AM, oldloc)
+ . = ..()
+ if(!isliving(AM) || bitecount) // can't pop opened chips
+ return
+
+ var/mob/living/popper = AM
+ if(popper.mob_size < MOB_SIZE_HUMAN)
+ return
+
+ playsound(src, 'sound/effects/chipbagpop.ogg', 100)
+ popper.visible_message("[popper] steps on \the [src], popping the bag!", "You step on \the [src], popping the bag!", "You hear a sharp crack!", COMBAT_MESSAGE_RANGE)
+ generate_trash(loc)
+ qdel(src)
diff --git a/code/modules/food_and_drinks/food/snacks_vend.dm b/code/modules/food_and_drinks/food/snacks_vend.dm
index 5d936ce6062..0a316c0fbe8 100644
--- a/code/modules/food_and_drinks/food/snacks_vend.dm
+++ b/code/modules/food_and_drinks/food/snacks_vend.dm
@@ -77,6 +77,20 @@
foodtype = JUNKFOOD | FRIED
value = FOOD_JUNK
+/obj/item/reagent_containers/food/snacks/chips/Crossed(atom/movable/AM, oldloc)
+ . = ..()
+ if(!isliving(AM) || bitecount) // can't pop opened chips
+ return
+
+ var/mob/living/popper = AM
+ if(popper.mob_size < MOB_SIZE_HUMAN)
+ return
+
+ playsound(src, 'sound/effects/chipbagpop.ogg', 100)
+ popper.visible_message("[popper] steps on \the [src], popping the bag!", "You step on \the [src], popping the bag!", "You hear a sharp crack!", COMBAT_MESSAGE_RANGE)
+ generate_trash(loc)
+ qdel(src)
+
/obj/item/reagent_containers/food/snacks/no_raisin
name = "4no raisins"
icon_state = "4no_raisins"
diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
index de26fc0e65c..71325e7395b 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
@@ -13,6 +13,7 @@
active_power_usage = 100
circuit = /obj/item/circuitboard/machine/smartfridge
+ var/base_build_path = /obj/machinery/smartfridge ///What path boards used to construct it should build into when dropped. Needed so we don't accidentally have them build variants with items preloaded in them.
var/max_n_of_items = 1500
var/allow_ai_retrieve = FALSE
var/list/initial_contents
@@ -242,6 +243,7 @@
idle_power_usage = 5
active_power_usage = 200
visible_contents = FALSE
+ base_build_path = /obj/machinery/smartfridge/drying_rack //should really be seeing this without admin fuckery.
var/drying = FALSE
/obj/machinery/smartfridge/drying_rack/Initialize()
@@ -356,6 +358,7 @@
/obj/machinery/smartfridge/drinks
name = "drink showcase"
desc = "A refrigerated storage unit for tasty tasty alcohol."
+ base_build_path = /obj/machinery/smartfridge/drinks
/obj/machinery/smartfridge/drinks/accept_check(obj/item/O)
if(!istype(O, /obj/item/reagent_containers) || (O.item_flags & ABSTRACT) || !O.reagents || !O.reagents.reagent_list.len)
@@ -368,6 +371,7 @@
// ----------------------------
/obj/machinery/smartfridge/food
desc = "A refrigerated storage unit for food."
+ base_build_path = /obj/machinery/smartfridge/food
/obj/machinery/smartfridge/food/accept_check(obj/item/O)
if(istype(O, /obj/item/reagent_containers/food/snacks/))
@@ -380,6 +384,7 @@
/obj/machinery/smartfridge/extract
name = "smart slime extract storage"
desc = "A refrigerated storage unit for slime extracts."
+ base_build_path = /obj/machinery/smartfridge/extract
/obj/machinery/smartfridge/extract/accept_check(obj/item/O)
if(istype(O, /obj/item/slime_extract))
@@ -398,6 +403,7 @@
name = "smart organ storage"
desc = "A refrigerated storage unit for organ storage."
max_n_of_items = 20 //vastly lower to prevent processing too long
+ base_build_path = /obj/machinery/smartfridge/organ
var/repair_rate = 0
/obj/machinery/smartfridge/organ/accept_check(obj/item/O)
@@ -437,6 +443,7 @@
/obj/machinery/smartfridge/chemistry
name = "smart chemical storage"
desc = "A refrigerated storage unit for medicine storage."
+ base_build_path = /obj/machinery/smartfridge/chemistry
/obj/machinery/smartfridge/chemistry/accept_check(obj/item/O)
var/static/list/chemfridge_typecache = typecacheof(list(
@@ -478,6 +485,7 @@
/obj/machinery/smartfridge/chemistry/virology
name = "smart virus storage"
desc = "A refrigerated storage unit for volatile sample storage."
+ base_build_path = /obj/machinery/smartfridge/chemistry/virology
/obj/machinery/smartfridge/chemistry/virology/preloaded
initial_contents = list(
@@ -499,6 +507,7 @@
icon_state = "disktoaster"
pass_flags = PASSTABLE
visible_contents = FALSE
+ base_build_path = /obj/machinery/smartfridge/disks
/obj/machinery/smartfridge/disks/accept_check(obj/item/O)
if(istype(O, /obj/item/disk/))
diff --git a/code/modules/food_and_drinks/recipes/drinks_recipes.dm b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
index de005307139..effc060444e 100644
--- a/code/modules/food_and_drinks/recipes/drinks_recipes.dm
+++ b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
@@ -323,14 +323,6 @@
results = list(/datum/reagent/consumable/ethanol/thirteenloko = 3)
required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 1, /datum/reagent/consumable/coffee = 1, /datum/reagent/consumable/limejuice = 1)
-/datum/chemical_reaction/chocolatepudding
- results = list(/datum/reagent/consumable/chocolatepudding = 20)
- required_reagents = list(/datum/reagent/consumable/milk/chocolate_milk = 10, /datum/reagent/consumable/eggyolk = 5)
-
-/datum/chemical_reaction/vanillapudding
- results = list(/datum/reagent/consumable/vanillapudding = 20)
- required_reagents = list(/datum/reagent/consumable/vanilla = 5, /datum/reagent/consumable/milk = 5, /datum/reagent/consumable/eggyolk = 5)
-
/datum/chemical_reaction/cherryshake
results = list(/datum/reagent/consumable/cherryshake = 3)
required_reagents = list(/datum/reagent/consumable/cherryjelly = 1, /datum/reagent/consumable/ice = 1, /datum/reagent/consumable/cream = 1)
diff --git a/code/modules/food_and_drinks/recipes/food_mixtures.dm b/code/modules/food_and_drinks/recipes/food_mixtures.dm
index ceed56e01d9..694af29d5d8 100644
--- a/code/modules/food_and_drinks/recipes/food_mixtures.dm
+++ b/code/modules/food_and_drinks/recipes/food_mixtures.dm
@@ -19,6 +19,14 @@
new /obj/item/reagent_containers/food/snacks/tofu(location)
return
+/datum/chemical_reaction/chocolatepudding
+ results = list(/datum/reagent/consumable/chocolatepudding = 20)
+ required_reagents = list(/datum/reagent/consumable/milk/chocolate_milk = 10, /datum/reagent/consumable/eggyolk = 5)
+
+/datum/chemical_reaction/vanillapudding
+ results = list(/datum/reagent/consumable/vanillapudding = 20)
+ required_reagents = list(/datum/reagent/consumable/vanilla = 5, /datum/reagent/consumable/milk = 5, /datum/reagent/consumable/eggyolk = 5)
+
/datum/chemical_reaction/chocolate_bar
required_reagents = list(/datum/reagent/consumable/soymilk = 2, /datum/reagent/consumable/coco = 2, /datum/reagent/consumable/sugar = 2)
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
index 8dfcb5b9097..94419a58c28 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
@@ -23,7 +23,7 @@
)
result = /obj/item/reagent_containers/food/snacks/donut/chaos
-datum/crafting_recipe/food/donut/meat
+/datum/crafting_recipe/food/donut/meat
time = 15
name = "Meat donut"
reqs = list(
diff --git a/code/modules/goonchat/browserassets/css/browserOutput.css b/code/modules/goonchat/browserassets/css/browserOutput.css
index 1a990391dec..135ef6ea1e5 100644
--- a/code/modules/goonchat/browserassets/css/browserOutput.css
+++ b/code/modules/goonchat/browserassets/css/browserOutput.css
@@ -298,7 +298,10 @@ em {font-style: normal; font-weight: bold;}
.emote { font-style: italic;}
.userdanger {color: #c51e1e; font-weight: bold; font-size: 185%;}
+.bolddanger {color: #c51e1e;font-weight: bold;}
.danger {color: #c51e1e;}
+.tinydanger {color: #c51e1e; font-size: 85%;}
+.smalldanger {color: #c51e1e; font-size: 90%;}
.warning {color: #c51e1e; font-style: italic;}
.alertwarning {color: #FF0000; font-weight: bold}
.boldwarning {color: #c51e1e; font-style: italic; font-weight: bold}
@@ -308,8 +311,9 @@ em {font-style: normal; font-weight: bold;}
.rose {color: #ff5050;}
.info {color: #9ab0ff;}
.notice {color: #6685f5;}
-.tinynotice {color: #6685f5; font-style: italic; font-size: 85%;}
-.smallnotice {color: #6685f5; font-style: italic; font-size: 90%;}
+.tinynotice {color: #6685f5; font-style: italic; font-size: 85%;}
+.smallnotice {color: #6685f5; font-size: 90%;}
+.smallnoticeital {color: #6685f5; font-style: italic; font-size: 90%;}
.boldnotice {color: #6685f5; font-weight: bold;}
.hear {color: #6685f5; font-style: italic;}
.adminnotice {color: #6685f5;}
diff --git a/code/modules/goonchat/browserassets/css/browserOutput_white.css b/code/modules/goonchat/browserassets/css/browserOutput_white.css
index 0bddfb761af..1c9d0cdfb29 100644
--- a/code/modules/goonchat/browserassets/css/browserOutput_white.css
+++ b/code/modules/goonchat/browserassets/css/browserOutput_white.css
@@ -296,7 +296,10 @@ h1.alert, h2.alert {color: #000000;}
.emote { font-style: italic;}
.userdanger {color: #ff0000; font-weight: bold; font-size: 185%;}
+.bolddanger {color: #ff0000; font-weight: bold;}
.danger {color: #ff0000;}
+.tinydanger {color: #ff0000; font-size: 85%;}
+.smalldanger {color: #ff0000; font-size: 90%;}
.warning {color: #ff0000; font-style: italic;}
.alertwarning {color: #FF0000; font-weight: bold}
.boldwarning {color: #ff0000; font-style: italic; font-weight: bold}
@@ -307,7 +310,8 @@ h1.alert, h2.alert {color: #000000;}
.info {color: #0000CC;}
.notice {color: #000099;}
.tinynotice {color: #000099; font-style: italic; font-size: 85%;}
-.smallnotice {color: #000099; font-style: italic; font-size: 90%;}
+.smallnotice {color: #000099; font-size: 90%;}
+.smallnoticeital {color: #000099; font-style: italic; font-size: 90%;}
.boldnotice {color: #000099; font-weight: bold;}
.hear {color: #000099; font-style: italic;}
.adminnotice {color: #0000ff;}
diff --git a/code/modules/holiday/easter.dm b/code/modules/holiday/easter.dm
index fce3f1160e9..3a72c46f181 100644
--- a/code/modules/holiday/easter.dm
+++ b/code/modules/holiday/easter.dm
@@ -61,26 +61,24 @@
unsuitable_atmos_damage = 0
//Easter Baskets
-/obj/item/storage/bag/easterbasket
+/obj/item/storage/basket/easter
name = "Easter Basket"
- icon = 'icons/mob/easter.dmi'
- icon_state = "basket"
-/obj/item/storage/bag/easterbasket/Initialize()
+/obj/item/storage/basket/easter/Initialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/egg, /obj/item/reagent_containers/food/snacks/chocolateegg, /obj/item/reagent_containers/food/snacks/boiledegg))
-/obj/item/storage/bag/easterbasket/proc/countEggs()
+/obj/item/storage/basket/easter/proc/countEggs()
cut_overlays()
add_overlay("basket-grass")
add_overlay("basket-egg[min(contents.len, 5)]")
-/obj/item/storage/bag/easterbasket/Exited()
+/obj/item/storage/basket/easter/Exited()
. = ..()
countEggs()
-/obj/item/storage/bag/easterbasket/Entered()
+/obj/item/storage/basket/easter/Entered()
. = ..()
countEggs()
diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm
index fc3a3810053..5f431f4cc7d 100644
--- a/code/modules/holiday/holidays.dm
+++ b/code/modules/holiday/holidays.dm
@@ -456,8 +456,16 @@
/datum/holiday/moth
name = "Moth Week"
-/datum/holiday/moth/shouldCelebrate(dd, mm, yy, ww, ddd) //National Moth Week falls on the last full week of July
- return mm == JULY && (ww == 4 || (ww == 5 && ddd == SUNDAY))
+/datum/holiday/moth/shouldCelebrate(dd, mm, yy, ww, ddd) //National Moth Week falls on the last full week of July, including the saturday and sunday before. See http://nationalmothweek.org/ for precise tracking.
+ if(mm == JULY)
+ var/week
+ if(first_day_of_month() >= 5) //Friday or later start of the month means week 5 is a full week.
+ week = 5
+ else
+ week = 4
+
+ return (ww == week-1 && (ddd == SATURDAY || ddd == SUNDAY)) || ww == week
+
/datum/holiday/moth/getStationPrefix()
return pick("Mothball","Lepidopteran","Lightbulb","Moth","Giant Atlas","Twin-spotted Sphynx","Madagascan Sunset","Luna","Death's Head","Emperor Gum","Polyphenus","Oleander Hawk","Io","Rosy Maple","Cecropia","Noctuidae","Giant Leopard","Dysphania Militaris","Garden Tiger")
@@ -587,7 +595,7 @@
GLOB.maintenance_loot += list(
list(
/obj/item/reagent_containers/food/snacks/egg/loaded = 15,
- /obj/item/storage/bag/easterbasket = 15
+ /obj/item/storage/basket/easter = 15
) = maint_holiday_weight,
)
diff --git a/code/modules/holodeck/area_copy.dm b/code/modules/holodeck/area_copy.dm
index 46a0fd38a6c..19a2615b731 100644
--- a/code/modules/holodeck/area_copy.dm
+++ b/code/modules/holodeck/area_copy.dm
@@ -1,7 +1,10 @@
//Vars that will not be copied when using /DuplicateObject
GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
"tag", "datum_components", "area", "type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key",
- "power_supply", "contents", "reagents", "stat", "x", "y", "z", "group", "atmos_adjacent_turfs", "comp_lookup"
+ "power_supply", "contents", "reagents", "stat", "x", "y", "z", "group", "atmos_adjacent_turfs", "comp_lookup",
+ "client_mobs_in_contents", "bodyparts", "internal_organs", "hand_bodyparts", "overlays", "overlays_standing", "hud_list",
+ "actions", "AIStatus", "appearance", "managed_overlays", "managed_vis_overlays", "computer_id", "lastKnownIP", "implants",
+ "tgui_shared_states"
))
/proc/DuplicateObject(atom/original, perfectcopy = TRUE, sameloc, atom/newloc = null, nerf, holoitem)
@@ -20,7 +23,7 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
if(islist(original.vars[V]))
var/list/L = original.vars[V]
O.vars[V] = L.Copy()
- else if(istype(original.vars[V], /datum))
+ else if(istype(original.vars[V], /datum) || ismob(original.vars[V]))
continue // this would reference the original's object, that will break when it is used or deleted.
else
O.vars[V] = original.vars[V]
@@ -52,6 +55,11 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
contained_atom.flags_1 |= HOLOGRAM_1
if(M.circuit)
M.circuit.flags_1 |= HOLOGRAM_1
+
+ if(ismob(O)) //Overlays are carried over despite disallowing them, if a fix is found remove this.
+ var/mob/M = O
+ M.cut_overlays()
+ M.regenerate_icons()
return O
diff --git a/code/modules/holodeck/turfs.dm b/code/modules/holodeck/turfs.dm
index 5aa0a15ba42..5305642e847 100644
--- a/code/modules/holodeck/turfs.dm
+++ b/code/modules/holodeck/turfs.dm
@@ -105,7 +105,7 @@
icon = 'icons/turf/floors/carpet.dmi'
icon_state = "carpet"
floor_tile = /obj/item/stack/tile/carpet
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_TRUE
canSmoothWith = null
bullet_bounce_sound = null
tiled_dirt = FALSE
@@ -117,7 +117,7 @@
/turf/open/floor/holofloor/carpet/update_icon()
. = ..()
if(intact)
- queue_smooth(src)
+ QUEUE_SMOOTH(src)
/turf/open/floor/holofloor/wood
icon_state = "wood"
diff --git a/code/modules/hydroponics/beekeeping/beebox.dm b/code/modules/hydroponics/beekeeping/beebox.dm
index b0bb30efb46..b290f07b79b 100644
--- a/code/modules/hydroponics/beekeeping/beebox.dm
+++ b/code/modules/hydroponics/beekeeping/beebox.dm
@@ -265,4 +265,4 @@
qdel(src)
/obj/structure/beebox/unwrenched
- anchored = FALSE
+ anchored = FALSE
diff --git a/code/modules/hydroponics/grafts.dm b/code/modules/hydroponics/grafts.dm
index 9f7a395d9c3..3af91b5ad3d 100644
--- a/code/modules/hydroponics/grafts.dm
+++ b/code/modules/hydroponics/grafts.dm
@@ -7,6 +7,7 @@
w_class = WEIGHT_CLASS_TINY
icon = 'icons/obj/hydroponics/equipment.dmi'
icon_state = "graft_plant"
+ worn_icon_state = "graft"
attack_verb = list("planted", "vegitized", "cropped", "reaped", "farmed")
///The stored trait taken from the parent plant. Defaults to perenial growth.
var/datum/plant_gene/trait/stored_trait
diff --git a/code/modules/hydroponics/grown/banana.dm b/code/modules/hydroponics/grown/banana.dm
index 1837e37cb0e..c0998045425 100644
--- a/code/modules/hydroponics/grown/banana.dm
+++ b/code/modules/hydroponics/grown/banana.dm
@@ -29,6 +29,12 @@
juice_results = list(/datum/reagent/consumable/banana = 0)
distill_reagent = /datum/reagent/consumable/ethanol/bananahonk
+/obj/item/reagent_containers/food/snacks/grown/banana/generate_trash(atom/location)
+ . = ..()
+ var/obj/item/grown/bananapeel/peel = .
+ if(istype(peel))
+ peel.grind_results = list(/datum/reagent/consumable/banana_peel = seed.potency * 0.2)
+
/obj/item/reagent_containers/food/snacks/grown/banana/suicide_act(mob/user)
user.visible_message("[user] is aiming [src] at [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!")
playsound(loc, 'sound/items/bikehorn.ogg', 50, TRUE, -1)
diff --git a/code/modules/hydroponics/grown/melon.dm b/code/modules/hydroponics/grown/melon.dm
index d462e7f430c..6383c3eca77 100644
--- a/code/modules/hydroponics/grown/melon.dm
+++ b/code/modules/hydroponics/grown/melon.dm
@@ -77,6 +77,19 @@
qdel(src)
new /obj/effect/decal/cleanable/ash(drop_location())
+/obj/item/reagent_containers/food/snacks/grown/holymelon/checkLiked(fraction, mob/M) //chaplains sure love holymelons
+ if(!ishuman(M))
+ return
+ if(last_check_time + 5 SECONDS >= world.time)
+ return
+ var/mob/living/carbon/human/holy_person = M
+ if(!holy_person.mind?.holy_role || HAS_TRAIT(holy_person, TRAIT_AGEUSIA))
+ return
+ to_chat(holy_person,"Truly, a piece of heaven!")
+ M.adjust_disgust(-5 + -2.5 * fraction)
+ SEND_SIGNAL(holy_person, COMSIG_ADD_MOOD_EVENT, "Divine_chew", /datum/mood_event/holy_consumption)
+ last_check_time = world.time
+
/// Barrel melon Seeds
/obj/item/seeds/watermelon/barrel
name = "pack of barrelmelon seeds"
diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm
index 73e6f6587f1..137a49b8ded 100644
--- a/code/modules/hydroponics/hydroitemdefines.dm
+++ b/code/modules/hydroponics/hydroitemdefines.dm
@@ -6,7 +6,7 @@
icon = 'icons/obj/device.dmi'
icon_state = "hydro"
inhand_icon_state = "analyzer"
- worn_icon_state = "analyzer"
+ worn_icon_state = "plantanalyzer"
lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
w_class = WEIGHT_CLASS_TINY
@@ -42,6 +42,7 @@
name = "weed spray"
icon_state = "weedspray"
inhand_icon_state = "spraycan"
+ worn_icon_state = "spraycan"
lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi'
volume = 100
@@ -57,6 +58,7 @@
name = "pest spray"
icon_state = "pestspray"
inhand_icon_state = "plantbgone"
+ worn_icon_state = "spraycan"
lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi'
volume = 100
@@ -125,7 +127,7 @@
custom_materials = list(/datum/material/iron = 15000)
attack_verb = list("chopped", "tore", "lacerated", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/hatchet/Initialize()
. = ..()
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index 82d2d027f62..1c576e4f552 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -5,6 +5,7 @@
/obj/item/seeds
icon = 'icons/obj/hydroponics/seeds.dmi'
icon_state = "seed" // Unknown plant seed - these shouldn't exist in-game.
+ worn_icon_state = "seed"
w_class = WEIGHT_CLASS_TINY
resistance_flags = FLAMMABLE
/// Name of plant when planted.
@@ -208,12 +209,17 @@
product_count = clamp(round(product_count/2),0,5)
while(t_amount < product_count)
var/obj/item/reagent_containers/food/snacks/grown/t_prod
- if(instability >= 30 && prob(instability/3) && mutatelist.len)
+ if(instability >= 30 && prob(instability/3) && mutatelist)
var/obj/item/seeds/new_prod = pick(mutatelist)
t_prod = initial(new_prod.product)
if(t_prod)
t_prod = new t_prod(output_loc, src)
- t_prod.seed.instability = instability/2
+ if(t_prod.seed)
+ t_prod.seed = initial(t_prod.seed)
+ t_prod.seed = new t_prod.seed
+ t_prod.seed.instability = round(instability/2)
+ t_amount++
+ continue
else
t_prod = new product(output_loc, src)
if(parent.myseed.plantname != initial(parent.myseed.plantname))
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index 851a132e3f0..3be5c79f232 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -65,6 +65,8 @@
var/display_order = JOB_DISPLAY_ORDER_DEFAULT
+ var/bounty_types = CIV_JOB_BASIC
+
//Only override this proc
//H is usually a human unless an /equip override transformed it
/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE)
@@ -148,7 +150,7 @@
/datum/job/proc/announce_head(mob/living/carbon/human/H, channels) //tells the given channel that the given mob is the new department head. See communications.dm for valid channels.
if(H && GLOB.announcement_systems.len)
//timer because these should come after the captain announcement
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1))
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/_addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1))
//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1
/datum/job/proc/player_old_enough(client/C)
diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm
index f53166d9b04..a132c8045a1 100644
--- a/code/modules/jobs/job_types/ai.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -1,8 +1,6 @@
/datum/job/ai
title = "AI"
- flag = AI_JF
auto_deadmin_role_flags = DEADMIN_POSITION_SILICON
- department_flag = ENGSEC
faction = "Station"
total_positions = 1
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm
index a314e780aa5..c66c2617ad3 100644
--- a/code/modules/jobs/job_types/assistant.dm
+++ b/code/modules/jobs/job_types/assistant.dm
@@ -3,8 +3,6 @@ Assistant
*/
/datum/job/assistant
title = "Assistant"
- flag = ASSISTANT
- department_flag = CIVILIAN
faction = "Station"
total_positions = 5
spawn_positions = 5
diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm
index dcfa77aa6b7..a11466aadb2 100644
--- a/code/modules/jobs/job_types/atmospheric_technician.dm
+++ b/code/modules/jobs/job_types/atmospheric_technician.dm
@@ -1,8 +1,6 @@
/datum/job/atmos
title = "Atmospheric Technician"
- flag = ATMOSTECH
department_head = list("Chief Engineer")
- department_flag = ENGSEC
faction = "Station"
total_positions = 3
spawn_positions = 2
@@ -19,6 +17,7 @@
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_ENG
display_order = JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN
+ bounty_types = CIV_JOB_ENG
/datum/outfit/job/atmos
name = "Atmospheric Technician"
diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm
index 1a83ede2a97..e74f54ff8d3 100644
--- a/code/modules/jobs/job_types/bartender.dm
+++ b/code/modules/jobs/job_types/bartender.dm
@@ -1,8 +1,6 @@
/datum/job/bartender
title = "Bartender"
- flag = BARTENDER
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 1
spawn_positions = 1
@@ -16,6 +14,7 @@
paycheck = PAYCHECK_EASY
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_BARTENDER
+ bounty_types = CIV_JOB_DRINK
/datum/outfit/job/bartender
name = "Bartender"
diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm
index f2b9e51afcc..d2c4d5b58b5 100644
--- a/code/modules/jobs/job_types/botanist.dm
+++ b/code/modules/jobs/job_types/botanist.dm
@@ -1,8 +1,6 @@
/datum/job/hydro
title = "Botanist"
- flag = BOTANIST
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 3
spawn_positions = 2
@@ -16,6 +14,7 @@
paycheck = PAYCHECK_EASY
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_BOTANIST
+ bounty_types = CIV_JOB_GROW
/datum/outfit/job/botanist
name = "Botanist"
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
index 24a11c31ce2..dbc0e888eaf 100755
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -1,9 +1,7 @@
/datum/job/captain
title = "Captain"
- flag = CAPTAIN
auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY
department_head = list("CentCom")
- department_flag = ENGSEC
faction = "Station"
total_positions = 1
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm
index 01171f8b728..27dba53f473 100644
--- a/code/modules/jobs/job_types/cargo_technician.dm
+++ b/code/modules/jobs/job_types/cargo_technician.dm
@@ -1,8 +1,6 @@
/datum/job/cargo_tech
title = "Cargo Technician"
- flag = CARGOTECH
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 3
spawn_positions = 2
diff --git a/code/modules/jobs/job_types/chaplain.dm b/code/modules/jobs/job_types/chaplain.dm
index caa42d72f97..dfd9dcd3d09 100644
--- a/code/modules/jobs/job_types/chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain.dm
@@ -1,8 +1,6 @@
/datum/job/chaplain
title = "Chaplain"
- flag = CHAPLAIN
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 1
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm
index ee1752aba2d..b051ecd0916 100644
--- a/code/modules/jobs/job_types/chemist.dm
+++ b/code/modules/jobs/job_types/chemist.dm
@@ -1,8 +1,6 @@
/datum/job/chemist
title = "Chemist"
- flag = CHEMIST
department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
faction = "Station"
total_positions = 2
spawn_positions = 2
@@ -19,6 +17,7 @@
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_CHEMIST
+ bounty_types = CIV_JOB_CHEM
/datum/outfit/job/chemist
name = "Chemist"
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index abf734d4585..21dde3fc0a9 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -1,9 +1,7 @@
/datum/job/chief_engineer
title = "Chief Engineer"
- flag = CHIEF
auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
department_head = list("Captain")
- department_flag = ENGSEC
head_announce = list("Engineering")
faction = "Station"
total_positions = 1
@@ -30,6 +28,7 @@
paycheck_department = ACCOUNT_ENG
display_order = JOB_DISPLAY_ORDER_CHIEF_ENGINEER
+ bounty_types = CIV_JOB_ENG
/datum/outfit/job/ce
name = "Chief Engineer"
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
index 837424887b5..7f90ab2ec50 100644
--- a/code/modules/jobs/job_types/chief_medical_officer.dm
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -1,8 +1,6 @@
/datum/job/cmo
title = "Chief Medical Officer"
- flag = CMO_JF
department_head = list("Captain")
- department_flag = MEDSCI
auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
head_announce = list(RADIO_CHANNEL_MEDICAL)
faction = "Station"
@@ -28,6 +26,7 @@
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER
+ bounty_types = CIV_JOB_MED
/datum/outfit/job/cmo
name = "Chief Medical Officer"
diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm
index 193d3e88293..c56f81df6c0 100644
--- a/code/modules/jobs/job_types/clown.dm
+++ b/code/modules/jobs/job_types/clown.dm
@@ -1,8 +1,6 @@
/datum/job/clown
title = "Clown"
- flag = CLOWN
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 1
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm
index a75a9bc0698..34d8ccfdd39 100644
--- a/code/modules/jobs/job_types/cook.dm
+++ b/code/modules/jobs/job_types/cook.dm
@@ -1,8 +1,6 @@
/datum/job/cook
title = "Cook"
- flag = COOK
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 2
spawn_positions = 1
@@ -18,6 +16,7 @@
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_COOK
+ bounty_types = CIV_JOB_CHEF
/datum/outfit/job/cook
name = "Cook"
diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm
index 3a4d9f46e85..85a700f3bd2 100644
--- a/code/modules/jobs/job_types/curator.dm
+++ b/code/modules/jobs/job_types/curator.dm
@@ -1,8 +1,6 @@
/datum/job/curator
title = "Curator"
- flag = CURATOR
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 1
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm
index 70d96cc0734..11d31268882 100644
--- a/code/modules/jobs/job_types/cyborg.dm
+++ b/code/modules/jobs/job_types/cyborg.dm
@@ -1,8 +1,6 @@
/datum/job/cyborg
title = "Cyborg"
- flag = CYBORG
auto_deadmin_role_flags = DEADMIN_POSITION_SILICON
- department_flag = ENGSEC
faction = "Station"
total_positions = 0
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm
index e1e55b225ce..0c14fe34287 100644
--- a/code/modules/jobs/job_types/detective.dm
+++ b/code/modules/jobs/job_types/detective.dm
@@ -1,9 +1,7 @@
/datum/job/detective
title = "Detective"
- flag = DETECTIVE
auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY
department_head = list("Head of Security")
- department_flag = ENGSEC
faction = "Station"
total_positions = 1
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm
index d3438ec405d..0817285697d 100644
--- a/code/modules/jobs/job_types/geneticist.dm
+++ b/code/modules/jobs/job_types/geneticist.dm
@@ -1,8 +1,6 @@
/datum/job/geneticist
title = "Geneticist"
- flag = GENETICIST
department_head = list("Research Director")
- department_flag = MEDSCI
faction = "Station"
total_positions = 2
spawn_positions = 2
@@ -19,6 +17,7 @@
paycheck_department = ACCOUNT_SCI
display_order = JOB_DISPLAY_ORDER_GENETICIST
+ bounty_types = CIV_JOB_SCI
/datum/outfit/job/geneticist
name = "Geneticist"
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
index 047f8bfe794..ecd73da6d03 100644
--- a/code/modules/jobs/job_types/head_of_personnel.dm
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -1,9 +1,7 @@
/datum/job/hop
title = "Head of Personnel"
- flag = HOP
auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
department_head = list("Captain")
- department_flag = CIVILIAN
head_announce = list(RADIO_CHANNEL_SUPPLY, RADIO_CHANNEL_SERVICE)
faction = "Station"
total_positions = 1
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
index 2105e65bd2e..cfcef505d84 100644
--- a/code/modules/jobs/job_types/head_of_security.dm
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -1,9 +1,7 @@
/datum/job/hos
title = "Head of Security"
- flag = HOS
auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY
department_head = list("Captain")
- department_flag = ENGSEC
head_announce = list(RADIO_CHANNEL_SECURITY)
faction = "Station"
total_positions = 1
@@ -31,6 +29,7 @@
paycheck_department = ACCOUNT_SEC
display_order = JOB_DISPLAY_ORDER_HEAD_OF_SECURITY
+ bounty_types = CIV_JOB_SEC
/datum/outfit/job/hos
name = "Head of Security"
diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm
index 8f13617155d..8b0fc510f87 100644
--- a/code/modules/jobs/job_types/janitor.dm
+++ b/code/modules/jobs/job_types/janitor.dm
@@ -1,8 +1,6 @@
/datum/job/janitor
title = "Janitor"
- flag = JANITOR
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 2
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm
index 1def5ee2099..9ad2d12e1dc 100644
--- a/code/modules/jobs/job_types/lawyer.dm
+++ b/code/modules/jobs/job_types/lawyer.dm
@@ -1,8 +1,6 @@
/datum/job/lawyer
title = "Lawyer"
- flag = LAWYER
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 2
spawn_positions = 2
diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm
index f47e00a2f07..27000068f62 100644
--- a/code/modules/jobs/job_types/medical_doctor.dm
+++ b/code/modules/jobs/job_types/medical_doctor.dm
@@ -1,8 +1,6 @@
/datum/job/doctor
title = "Medical Doctor"
- flag = DOCTOR
department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
faction = "Station"
total_positions = 5
spawn_positions = 3
@@ -17,6 +15,7 @@
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_MEDICAL_DOCTOR
+ bounty_types = CIV_JOB_MED
/datum/outfit/job/doctor
name = "Medical Doctor"
diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm
index bbd59f75f8e..f8a81859c2b 100644
--- a/code/modules/jobs/job_types/mime.dm
+++ b/code/modules/jobs/job_types/mime.dm
@@ -1,8 +1,6 @@
/datum/job/mime
title = "Mime"
- flag = MIME
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 1
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/paramedic.dm b/code/modules/jobs/job_types/paramedic.dm
index 57ea23f7ed5..3376ae3bb8b 100644
--- a/code/modules/jobs/job_types/paramedic.dm
+++ b/code/modules/jobs/job_types/paramedic.dm
@@ -1,8 +1,6 @@
/datum/job/paramedic
title = "Paramedic"
- flag = PARAMEDIC
department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
faction = "Station"
total_positions = 2
spawn_positions = 2
@@ -17,6 +15,7 @@
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_PARAMEDIC
+ bounty_types = CIV_JOB_MED
/datum/outfit/job/paramedic
name = "Paramedic"
diff --git a/code/modules/jobs/job_types/prisoner.dm b/code/modules/jobs/job_types/prisoner.dm
index d0ab7a5c00e..0b79464487f 100644
--- a/code/modules/jobs/job_types/prisoner.dm
+++ b/code/modules/jobs/job_types/prisoner.dm
@@ -1,14 +1,12 @@
/datum/job/prisoner
title = "Prisoner"
- flag = PRISONER
department_head = list("The Security Team")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 0
spawn_positions = 2
supervisors = "the security team"
selection_color = "#ffe1c3"
-
+ paycheck = PAYCHECK_PRISONER
outfit = /datum/outfit/job/prisoner
display_order = JOB_DISPLAY_ORDER_PRISONER
diff --git a/code/modules/jobs/job_types/psychologist.dm b/code/modules/jobs/job_types/psychologist.dm
index 73ceaaacdd2..5d3411b3611 100644
--- a/code/modules/jobs/job_types/psychologist.dm
+++ b/code/modules/jobs/job_types/psychologist.dm
@@ -1,8 +1,6 @@
/datum/job/psychologist
title = "Psychologist"
- flag = PSYCHOLOGIST
department_head = list("Head of Personnel","Chief Medical Officer")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 1
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm
index 90158995a98..76ff70d5f83 100644
--- a/code/modules/jobs/job_types/quartermaster.dm
+++ b/code/modules/jobs/job_types/quartermaster.dm
@@ -1,8 +1,6 @@
/datum/job/qm
title = "Quartermaster"
- flag = QUARTERMASTER
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 1
spawn_positions = 1
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
index 12dbb24db97..bf355a30d40 100644
--- a/code/modules/jobs/job_types/research_director.dm
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -1,9 +1,7 @@
/datum/job/rd
title = "Research Director"
- flag = RD_JF
auto_deadmin_role_flags = DEADMIN_POSITION_HEAD
department_head = list("Captain")
- department_flag = MEDSCI
head_announce = list("Science")
faction = "Station"
total_positions = 1
@@ -34,6 +32,7 @@
paycheck_department = ACCOUNT_SCI
display_order = JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR
+ bounty_types = CIV_JOB_SCI
/datum/outfit/job/rd
name = "Research Director"
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
index 1b1b4425a98..0ff5a1a6c84 100644
--- a/code/modules/jobs/job_types/roboticist.dm
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -1,8 +1,6 @@
/datum/job/roboticist
title = "Roboticist"
- flag = ROBOTICIST
department_head = list("Research Director")
- department_flag = MEDSCI
faction = "Station"
total_positions = 2
spawn_positions = 2
@@ -10,6 +8,7 @@
selection_color = "#ffeeff"
exp_requirements = 60
exp_type = EXP_TYPE_CREW
+ bounty_types = CIV_JOB_ROBO
outfit = /datum/outfit/job/roboticist
diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm
index a0038e7c097..9be376552ee 100644
--- a/code/modules/jobs/job_types/scientist.dm
+++ b/code/modules/jobs/job_types/scientist.dm
@@ -1,8 +1,6 @@
/datum/job/scientist
title = "Scientist"
- flag = SCIENTIST
department_head = list("Research Director")
- department_flag = MEDSCI
faction = "Station"
total_positions = 5
spawn_positions = 3
@@ -20,6 +18,7 @@
paycheck_department = ACCOUNT_SCI
display_order = JOB_DISPLAY_ORDER_SCIENTIST
+ bounty_types = CIV_JOB_SCI
/datum/outfit/job/scientist
name = "Scientist"
diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm
index dd005b89f69..4ae1cb15efa 100644
--- a/code/modules/jobs/job_types/security_officer.dm
+++ b/code/modules/jobs/job_types/security_officer.dm
@@ -1,9 +1,7 @@
/datum/job/officer
title = "Security Officer"
- flag = OFFICER
auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY
department_head = list("Head of Security")
- department_flag = ENGSEC
faction = "Station"
total_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
spawn_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
@@ -22,6 +20,7 @@
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_SECURITY_OFFICER
+ bounty_types = CIV_JOB_SEC
/datum/job/officer/get_access()
var/list/L = list()
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
index e00e6c821d7..50e3d8abdb6 100644
--- a/code/modules/jobs/job_types/shaft_miner.dm
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -1,8 +1,6 @@
/datum/job/mining
title = "Shaft Miner"
- flag = MINER
department_head = list("Head of Personnel")
- department_flag = CIVILIAN
faction = "Station"
total_positions = 3
spawn_positions = 3
@@ -13,10 +11,11 @@
access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MECH_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_MINING, ACCESS_MECH_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
- paycheck = PAYCHECK_HARD
+ paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_CAR
display_order = JOB_DISPLAY_ORDER_SHAFT_MINER
+ bounty_types = CIV_JOB_MINE
/datum/outfit/job/miner
name = "Shaft Miner"
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
index aad31434142..9eec843b1d5 100644
--- a/code/modules/jobs/job_types/station_engineer.dm
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -1,8 +1,6 @@
/datum/job/engineer
title = "Station Engineer"
- flag = ENGINEER
department_head = list("Chief Engineer")
- department_flag = ENGSEC
faction = "Station"
total_positions = 5
spawn_positions = 5
@@ -21,6 +19,7 @@
paycheck_department = ACCOUNT_ENG
display_order = JOB_DISPLAY_ORDER_STATION_ENGINEER
+ bounty_types = CIV_JOB_ENG
/datum/outfit/job/engineer
name = "Station Engineer"
diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm
index 85eaab2e46c..28bf1319ba9 100644
--- a/code/modules/jobs/job_types/virologist.dm
+++ b/code/modules/jobs/job_types/virologist.dm
@@ -1,8 +1,6 @@
/datum/job/virologist
title = "Virologist"
- flag = VIROLOGIST
department_head = list("Chief Medical Officer")
- department_flag = MEDSCI
faction = "Station"
total_positions = 1
spawn_positions = 1
@@ -19,6 +17,7 @@
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_VIROLOGIST
+ bounty_types = CIV_JOB_VIRO
/datum/outfit/job/virologist
name = "Virologist"
diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm
index 679f24752fe..9eb1bc03936 100644
--- a/code/modules/jobs/job_types/warden.dm
+++ b/code/modules/jobs/job_types/warden.dm
@@ -1,9 +1,7 @@
/datum/job/warden
title = "Warden"
- flag = WARDEN
auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY
department_head = list("Head of Security")
- department_flag = ENGSEC
faction = "Station"
total_positions = 1
spawn_positions = 1
@@ -22,6 +20,7 @@
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_WARDEN
+ bounty_types = CIV_JOB_SEC
/datum/job/warden/get_access()
var/list/L = list()
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index c44cead1cfe..520d6d91b91 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -88,7 +88,7 @@
// can hold different keys and releasing any should be handled by the key binding specifically
for (var/kb_name in prefs.key_bindings[_key])
var/datum/keybinding/kb = GLOB.keybindings_by_name[kb_name]
- if(kb.up(src))
+ if(kb.can_use(src) && kb.up(src))
break
holder?.key_up(_key, src)
mob.focus?.key_up(_key, src)
diff --git a/code/modules/library/lib_codex_gigas.dm b/code/modules/library/lib_codex_gigas.dm
index be017a07ef6..13047c54521 100644
--- a/code/modules/library/lib_codex_gigas.dm
+++ b/code/modules/library/lib_codex_gigas.dm
@@ -71,7 +71,7 @@
return FALSE
if(action == "search")
SStgui.close_uis(src)
- addtimer(CALLBACK(src, .proc/perform_research, usr, currentName), 0)
+ INVOKE_ASYNC(src, .proc/perform_research, usr, currentName)
currentName = ""
currentSection = PRE_TITLE
return FALSE
diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm
index 13d9aa4a19b..c1415b1e254 100644
--- a/code/modules/library/lib_items.dm
+++ b/code/modules/library/lib_items.dm
@@ -1,3 +1,7 @@
+#define BOOKCASE_UNANCHORED 0
+#define BOOKCASE_ANCHORED 1
+#define BOOKCASE_FINISHED 2
+
/* Library Items
*
* Contains:
@@ -21,8 +25,7 @@
resistance_flags = FLAMMABLE
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0)
- var/state = 0
- var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book) //Things allowed in the bookcase
+ var/state = BOOKCASE_UNANCHORED
/// When enabled, books_to_load number of random books will be generated for this bookcase when first interacted with.
var/load_random_books = FALSE
/// The category of books to pick from when populating random books.
@@ -37,55 +40,66 @@
else
. += "It's secured in place with bolts."
switch(state)
- if(0)
+ if(BOOKCASE_UNANCHORED)
. += "There's a small crack visible on the back panel."
- if(1)
+ if(BOOKCASE_ANCHORED)
. += "There's space inside for a wooden shelf."
- if(2)
+ if(BOOKCASE_FINISHED)
. += "There's a small crack visible on the shelf."
/obj/structure/bookcase/Initialize(mapload)
. = ..()
if(!mapload)
return
- state = 2
- icon_state = "book-0"
- anchored = TRUE
+ set_anchored(TRUE)
+ state = BOOKCASE_FINISHED
for(var/obj/item/I in loc)
- if(istype(I, /obj/item/book))
- I.forceMove(src)
+ if(!isbook(I))
+ continue
+ I.forceMove(src)
+ update_icon()
+
+/obj/structure/bookcase/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return
+ state = anchorvalue
+ if(!anchorvalue) //in case we were vareditted or uprooted by a hostile mob, ensure we drop all our books instead of having them disappear till we're rebuild.
+ var/atom/Tsec = drop_location()
+ for(var/obj/I in contents)
+ if(!isbook(I))
+ continue
+ I.forceMove(Tsec)
update_icon()
/obj/structure/bookcase/attackby(obj/item/I, mob/user, params)
switch(state)
- if(0)
+ if(BOOKCASE_UNANCHORED)
if(I.tool_behaviour == TOOL_WRENCH)
if(I.use_tool(src, user, 20, volume=50))
to_chat(user, "You wrench the frame into place.")
- anchored = TRUE
- state = 1
- if(I.tool_behaviour == TOOL_CROWBAR)
+ set_anchored(TRUE)
+ else if(I.tool_behaviour == TOOL_CROWBAR)
if(I.use_tool(src, user, 20, volume=50))
to_chat(user, "You pry the frame apart.")
deconstruct(TRUE)
- if(1)
+ if(BOOKCASE_ANCHORED)
if(istype(I, /obj/item/stack/sheet/mineral/wood))
var/obj/item/stack/sheet/mineral/wood/W = I
if(W.get_amount() >= 2)
W.use(2)
to_chat(user, "You add a shelf.")
- state = 2
- icon_state = "book-0"
- if(I.tool_behaviour == TOOL_WRENCH)
+ state = BOOKCASE_FINISHED
+ update_icon()
+ else if(I.tool_behaviour == TOOL_WRENCH)
I.play_tool_sound(src, 100)
to_chat(user, "You unwrench the frame.")
- anchored = FALSE
- state = 0
+ set_anchored(FALSE)
- if(2)
+ if(BOOKCASE_FINISHED)
var/datum/component/storage/STR = I.GetComponent(/datum/component/storage)
- if(is_type_in_list(I, allowed_books))
+ if(isbook(I))
if(!user.transferItemToLoc(I, src))
return
update_icon()
@@ -113,8 +127,8 @@
I.play_tool_sound(src, 100)
to_chat(user, "You pry the shelf out.")
new /obj/item/stack/sheet/mineral/wood(drop_location(), 2)
- state = 1
- icon_state = "bookempty"
+ state = BOOKCASE_ANCHORED
+ update_icon()
else
return ..()
@@ -142,17 +156,24 @@
/obj/structure/bookcase/deconstruct(disassembled = TRUE)
- new /obj/item/stack/sheet/mineral/wood(loc, 4)
- for(var/obj/item/book/B in contents)
- B.forceMove(get_turf(src))
- qdel(src)
+ var/atom/Tsec = drop_location()
+ new /obj/item/stack/sheet/mineral/wood(Tsec, 4)
+ for(var/obj/item/I in contents)
+ if(!isbook(I))
+ continue
+ I.forceMove(Tsec)
+ return ..()
/obj/structure/bookcase/update_icon_state()
+ if(state == BOOKCASE_UNANCHORED)
+ icon_state = "bookempty"
+ return
+
var/amount = contents.len
if(load_random_books)
amount += books_to_load
- icon_state = "book-[amount < 5 ? amount : 5]"
+ icon_state = "book-[clamp(amount, 0, 5)]"
/obj/structure/bookcase/manuals/engineering
@@ -184,6 +205,7 @@
name = "book"
icon = 'icons/obj/library.dmi'
icon_state ="book"
+ worn_icon_state = "book"
desc = "Crack it open, inhale the musk of its pages, and learn something new."
throw_speed = 1
throw_range = 5
@@ -351,3 +373,8 @@
else
to_chat(user, "No associated computer found. Only local scans will function properly.")
to_chat(user, "\n")
+
+
+#undef BOOKCASE_UNANCHORED
+#undef BOOKCASE_ANCHORED
+#undef BOOKCASE_FINISHED
diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm
index 0b164f5f66d..81f6d99e750 100644
--- a/code/modules/library/lib_machines.dm
+++ b/code/modules/library/lib_machines.dm
@@ -392,7 +392,7 @@
if(checkoutperiod < 1)
checkoutperiod = 1
if(href_list["editbook"])
- buffer_book = stripped_input(usr, "Enter the book's title:")
+ buffer_book = stripped_input(usr, "Enter the book's title:", max_length = 45)
if(href_list["editmob"])
buffer_mob = stripped_input(usr, "Enter the recipient's name:", max_length = MAX_NAME_LEN)
if(href_list["checkout"])
@@ -411,7 +411,7 @@
if(b && istype(b))
inventory.Remove(b)
if(href_list["setauthor"])
- var/newauthor = stripped_input(usr, "Enter the author's name: ")
+ var/newauthor = stripped_input(usr, "Enter the author's name: ", max_length = 45)
if(newauthor)
scanner.cache.author = newauthor
if(href_list["setcategory"])
diff --git a/code/modules/library/skill_learning/skill_station.dm b/code/modules/library/skill_learning/skill_station.dm
new file mode 100644
index 00000000000..0efc7b69486
--- /dev/null
+++ b/code/modules/library/skill_learning/skill_station.dm
@@ -0,0 +1,208 @@
+#define SKILLCHIP_IMPLANT_TIME 1 MINUTES
+#define SKILLCHIP_REMOVAL_TIME 30 SECONDS
+
+/obj/machinery/skill_station
+ name = "Skillsoft Station"
+ desc = "learn skills with only minimal chance for brain damage."
+
+ icon = 'icons/obj/machines/implantchair.dmi'
+ icon_state = "implantchair"
+ occupant_typecache = list(/mob/living/carbon) //todo make occupant_typecache per type
+ state_open = TRUE
+ interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND //Don't call ui_interac by default - we only want that when inside
+ circuit = /obj/item/circuitboard/machine/skill_station
+ /// Currently implanting/removing
+ var/working = FALSE
+ /// Timer until implanting/removing finishes.
+ var/work_timer
+ /// What we're implanting
+ var/obj/item/skillchip/inserted_skillchip
+
+/obj/machinery/skill_station/Initialize()
+ . = ..()
+ update_icon()
+
+//Only usable by the person inside
+/obj/machinery/skill_station/ui_state(mob/user)
+ return GLOB.contained_state
+
+/obj/machinery/skill_station/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "SkillStation", name)
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/obj/machinery/skill_station/update_icon_state()
+ icon_state = initial(icon_state)
+ if(state_open)
+ icon_state += "_open"
+ if(occupant)
+ icon_state += "_occupied"
+
+/obj/machinery/skill_station/update_overlays()
+ . = ..()
+ if(working)
+ . += "working"
+
+/obj/machinery/skill_station/relaymove(mob/user)
+ open_machine()
+
+/obj/machinery/skill_station/open_machine()
+ . = ..()
+ interrupt_operation()
+
+/obj/machinery/skill_station/Exited(atom/movable/AM, atom/newloc)
+ . = ..()
+ if(AM == inserted_skillchip)
+ inserted_skillchip = null
+ interrupt_operation()
+
+/obj/machinery/skill_station/power_change()
+ . = ..()
+ if(working)
+ interrupt_operation()
+
+/obj/machinery/skill_station/close_machine(atom/movable/target)
+ . = ..()
+ if(occupant)
+ ui_interact(occupant)
+
+/obj/machinery/skill_station/proc/interrupt_operation()
+ working = FALSE
+ if(work_timer)
+ deltimer(work_timer)
+ work_timer = null
+ update_icon()
+
+/obj/machinery/skill_station/interact(mob/user)
+ . = ..()
+ if(user == occupant)
+ ui_interact(user)
+ else
+ toggle_open()
+
+/obj/machinery/skill_station/attackby(obj/item/I, mob/living/user, params)
+ if(istype(I,/obj/item/skillchip))
+ if(inserted_skillchip)
+ to_chat(user,"There's already a skillchip inside.")
+ return
+ if(!user.transferItemToLoc(I, src))
+ return
+ inserted_skillchip = I
+ SStgui.update_uis(src)
+ return
+ return ..()
+
+/obj/machinery/skill_station/dropContents(list/subset)
+ subset = contents - inserted_skillchip
+ return ..() //This is kinda annoying
+
+/obj/machinery/skill_station/proc/toggle_open(mob/user)
+ state_open ? close_machine() : open_machine()
+
+// Functions below do not validate occupant exists - should be handled outer wrappers.
+/// Start implanting.
+/obj/machinery/skill_station/proc/start_implanting()
+ if(!inserted_skillchip?.can_be_implanted(occupant))
+ return
+ working = TRUE
+ work_timer = addtimer(CALLBACK(src,.proc/implant),SKILLCHIP_IMPLANT_TIME,TIMER_STOPPABLE)
+ update_icon()
+
+/// Finish implanting.
+/obj/machinery/skill_station/proc/implant()
+ working = FALSE
+ work_timer = null
+ if(inserted_skillchip?.can_be_implanted(occupant))
+ implant_skillchip(occupant,inserted_skillchip)
+ update_icon()
+ SStgui.update_uis(src)
+ to_chat(occupant,"Operation complete!")
+
+/// Start removal.
+/obj/machinery/skill_station/proc/start_removal(obj/item/skillchip/to_be_removed)
+ if(!to_be_removed)
+ return
+ working = TRUE
+ work_timer = addtimer(CALLBACK(src,.proc/remove_skillchip,to_be_removed),SKILLCHIP_REMOVAL_TIME,TIMER_STOPPABLE)
+ update_icon()
+
+/// Finish removal.
+/obj/machinery/skill_station/proc/remove_skillchip(obj/item/skillchip/to_be_removed)
+ working = FALSE
+ work_timer = null
+ var/mob/living/carbon/carbon_occupant = occupant
+ var/obj/item/organ/brain/occupant_brain = carbon_occupant.getorganslot(ORGAN_SLOT_BRAIN)
+ if(QDELETED(carbon_occupant) || QDELETED(occupant_brain) || !(to_be_removed in occupant_brain.skillchips))
+ return
+ to_be_removed.on_removal(carbon_occupant, silent=FALSE)
+ LAZYREMOVE(occupant_brain.skillchips, to_be_removed)
+ if(to_be_removed.removable)
+ carbon_occupant.put_in_hands(to_be_removed)
+ else
+ qdel(to_be_removed)
+ update_icon()
+ SStgui.update_uis(src)
+ to_chat(carbon_occupant,"Operation complete!")
+
+/obj/machinery/skill_station/proc/implant_skillchip(mob/living/carbon/target,obj/item/skillchip/chip)
+ var/obj/item/organ/brain/target_brain = target.getorganslot(ORGAN_SLOT_BRAIN)
+ chip.on_apply(target, silent = FALSE)
+ chip.forceMove(target_brain)
+ LAZYADD(target_brain.skillchips, chip)
+
+/obj/machinery/skill_station/ui_data(mob/user)
+ . = ..()
+ .["working"] = working
+ .["timeleft"] = work_timer ? timeleft(work_timer) : null
+ var/mob/living/carbon/carbon_occupant = occupant
+ var/obj/item/organ/brain/occupant_brain = carbon_occupant.getorganslot(ORGAN_SLOT_BRAIN)
+ if(QDELETED(carbon_occupant) || QDELETED(occupant_brain))
+ .["error"] = "Brain not detected. Please consult nearest medical practitioner."
+ else
+ var/list/current_skills = list()
+ for(var/obj/item/skillchip/skill_chip in occupant_brain)
+ current_skills += list(list("name"=skill_chip.skill_name,"icon"=skill_chip.skill_icon,"cost"=skill_chip.slot_cost,"ref"=REF(skill_chip),"active"=(skill_chip in occupant_brain.skillchips)))
+ .["current"] = current_skills
+ .["slots_used"] = carbon_occupant.used_skillchip_slots
+ .["slots_max"] = carbon_occupant.max_skillchip_slots
+
+ .["skillchip_ready"] = inserted_skillchip ? TRUE : FALSE
+ if(inserted_skillchip)
+ .["implantable"] = inserted_skillchip.can_be_implanted(occupant)
+ .["implantable_reason"] = inserted_skillchip.can_be_implanted_message(occupant)
+ .["skill_name"] = inserted_skillchip.skill_name
+ .["skill_desc"] = inserted_skillchip.skill_description
+ .["skill_icon"] = inserted_skillchip.skill_icon
+ .["skill_cost"] = inserted_skillchip.slot_cost
+
+/obj/machinery/skill_station/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+ if(usr != occupant)
+ return
+ switch(action)
+ if("implant")
+ if(occupant && inserted_skillchip)
+ start_implanting()
+ return TRUE
+ if("remove")
+ var/chipref = params["ref"]
+ var/mob/living/carbon/carbon_occupant = occupant
+ var/obj/item/organ/brain/occupant_brain = carbon_occupant.getorganslot(ORGAN_SLOT_BRAIN)
+ if(QDELETED(carbon_occupant) || QDELETED(occupant_brain))
+ return TRUE
+ var/obj/item/skillchip/to_be_removed = locate(chipref) in occupant_brain
+ if(!to_be_removed)
+ return TRUE
+ start_removal(to_be_removed)
+ return TRUE
+ if("eject")
+ if(inserted_skillchip)
+ to_chat(occupant,"You eject the skillchip.")
+ var/mob/living/carbon/human/H = occupant
+ H.put_in_hands(inserted_skillchip)
+ inserted_skillchip = null
+ return TRUE
diff --git a/code/modules/library/skill_learning/skillchip.dm b/code/modules/library/skill_learning/skillchip.dm
new file mode 100644
index 00000000000..1f350c674ca
--- /dev/null
+++ b/code/modules/library/skill_learning/skillchip.dm
@@ -0,0 +1,98 @@
+/obj/item/skillchip
+ name = "skillchip"
+ desc = "This biochip integrates with user's brain to enable mastery of specific skill. Consult certified Nanotrasen neurosurgeon before use."
+
+ icon = 'icons/obj/card.dmi'
+ icon_state = "data_3"
+ custom_price = 500
+ w_class = WEIGHT_CLASS_SMALL
+
+ /// Trait automatically granted by this chip, optional
+ var/auto_trait
+ /// Skill name shown on UI
+ var/skill_name
+ /// Skill description shown on UI
+ var/skill_description
+ /// FS icon show on UI
+ var/skill_icon = "brain"
+ /// Message shown when implanting the chip
+ var/implanting_message
+ /// Message shown when extracting the chip
+ var/removal_message
+ //If set to TRUE, trying to extract the chip will destroy it instead
+ var/removable = TRUE
+ /// How many skillslots this one takes
+ var/slot_cost = 1
+
+/// Called after implantation and/or brain entering new body
+/obj/item/skillchip/proc/on_apply(mob/living/carbon/user,silent=TRUE)
+ if(!silent && implanting_message)
+ to_chat(user,implanting_message)
+ if(auto_trait)
+ ADD_TRAIT(user,auto_trait,SKILLCHIP_TRAIT)
+ user.used_skillchip_slots += slot_cost
+
+/// Called after removal and/or brain exiting the body
+/obj/item/skillchip/proc/on_removal(mob/living/carbon/user,silent=TRUE)
+ if(!silent && removal_message)
+ to_chat(user,removal_message)
+ if(auto_trait)
+ REMOVE_TRAIT(user,auto_trait,SKILLCHIP_TRAIT)
+ user.used_skillchip_slots -= slot_cost
+
+/// Checks if this implant is valid to implant in a given mob.
+/obj/item/skillchip/proc/can_be_implanted(mob/living/carbon/target)
+ //No brain
+ var/obj/item/organ/brain/target_brain = target.getorganslot(ORGAN_SLOT_BRAIN)
+ if(QDELETED(target_brain))
+ return FALSE
+ //No skill slots left
+ if(target.used_skillchip_slots + slot_cost > target.max_skillchip_slots)
+ return FALSE
+ //Only one copy of each for now.
+ if(locate(type) in target_brain.skillchips)
+ return FALSE
+ return TRUE
+
+/// Returns readable reason why implanting cannot succeed --todo switch to flag retval in can_be_implanted to cut down copypaste
+/obj/item/skillchip/proc/can_be_implanted_message(mob/living/carbon/target)
+ //No brain
+ var/obj/item/organ/brain/target_brain = target.getorganslot(ORGAN_SLOT_BRAIN)
+ if(QDELETED(target_brain))
+ return "No brain detected."
+ //No skill slots left
+ if(target.used_skillchip_slots + slot_cost > target.max_skillchip_slots)
+ return "Complexity limit exceeded."
+ //Only one copy of each for now.
+ if(locate(type) in target_brain.skillchips)
+ return "Duplicate chip detected."
+ return "Chip ready for implantation."
+
+/obj/item/skillchip/basketweaving
+ name = "Basketsoft 3000 skillchip"
+ desc = "Underwater edition."
+ auto_trait = TRAIT_UNDERWATER_BASKETWEAVING_KNOWLEDGE
+ skill_name = "Underwater Basketweaving"
+ skill_description = "Master intricate art of using twine to create perfect baskets while submerged."
+ skill_icon = "shopping-basket"
+ implanting_message = "You're one with the twine and the sea."
+ removal_message = "Higher mysteries of underwater basketweaving leave your mind."
+
+/obj/item/skillchip/wine_taster
+ name = "WINE skillchip"
+ desc = "Wine.Is.Not.Equal version 5."
+ auto_trait = TRAIT_WINE_TASTER
+ skill_name = "Wine Tasting"
+ skill_description = "Recognize wine vintage from taste alone. Never again lack an opinion when presented with an unknown drink."
+ skill_icon = "wine-bottle"
+ implanting_message = "You recall wine taste."
+ removal_message = "Your memories of wine evaporate."
+
+/obj/item/skillchip/bonsai
+ name = "Hedge 3 skillchip"
+ auto_trait = TRAIT_BONSAI
+ skill_name = "Hedgetrimming"
+ skill_description = "Trim hedges and potted plants into marvelous new shapes with any old knife. Not applicable to plastic plants."
+ skill_icon = "spa"
+ implanting_message = "Your mind is filled with plant arrangments."
+ removal_message = "Your can't remember how a hedge looks like anymore."
diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm
index 05719b4d248..8e6617ab490 100644
--- a/code/modules/lighting/lighting_atom.dm
+++ b/code/modules/lighting/lighting_atom.dm
@@ -48,15 +48,6 @@
else
light = new/datum/light_source(src, .)
-// If we have opacity, make sure to tell (potentially) affected light sources.
-/atom/movable/Destroy()
- var/turf/T = loc
- . = ..()
- if (opacity && istype(T))
- var/old_has_opaque_atom = T.has_opaque_atom
- T.recalc_atom_opacity()
- if (old_has_opaque_atom != T.has_opaque_atom)
- T.reconsider_lights()
// Should always be used to change the opacity of an atom.
// It notifies (potentially) affected light sources so they can update (if needed).
diff --git a/code/modules/lighting/lighting_object.dm b/code/modules/lighting/lighting_object.dm
index e219f08e159..46688b5e97a 100644
--- a/code/modules/lighting/lighting_object.dm
+++ b/code/modules/lighting/lighting_object.dm
@@ -144,7 +144,7 @@
/atom/movable/lighting_object/onTransitZ()
return
-/atom/movable/lighting_object/washed(var/washer)
+/atom/movable/lighting_object/wash(clean_types)
return
// Override here to prevent things accidentally moving around overlays.
diff --git a/code/modules/mafia/_defines.dm b/code/modules/mafia/_defines.dm
index b862ce63c5c..e38757f578c 100644
--- a/code/modules/mafia/_defines.dm
+++ b/code/modules/mafia/_defines.dm
@@ -1,7 +1,30 @@
+///how many people can play mafia without issues (running out of spawns, procs not expecting more than this amount of people, etc)
+#define MAFIA_MAX_PLAYER_COUNT 12
+
#define MAFIA_TEAM_TOWN "town"
#define MAFIA_TEAM_MAFIA "mafia"
#define MAFIA_TEAM_SOLO "solo"
+//types of town roles for random setup gen
+/// assistants it's just assistants filling up the rest of the roles
+#define TOWN_OVERFLOW "overflow"
+/// roles that learn info about others in the game (chaplain, detective, psych)
+#define TOWN_INVEST "invest"
+/// roles that keep other roles safe (doctor, and weirdly enough lawyer counts)
+#define TOWN_PROTECT "protect"
+/// roles that don't fit into anything else (hop)
+#define TOWN_MISC "misc"
+
+//other types (mafia team, neutrals)
+/// normal vote kill changelings
+#define MAFIA_REGULAR "regular"
+/// every other changeling role that has extra abilities
+#define MAFIA_SPECIAL "special"
+/// role that wins solo that nobody likes
+#define NEUTRAL_KILL "kill"
+/// role that upsets the game aka obsessed, usually worse for town than mafia but they can vote against mafia
+#define NEUTRAL_DISRUPT "disrupt"
+
#define MAFIA_PHASE_SETUP 1
#define MAFIA_PHASE_DAY 2
#define MAFIA_PHASE_VOTING 3
@@ -20,130 +43,21 @@
//in order of events + game end
-#define COMSIG_MAFIA_SUNDOWN "sundown" //the rest of these phases are at the end of the night when shutters raise, in a different order of resolution
+/// when the shutters fall, before the 45 second wait and night event resolution
+#define COMSIG_MAFIA_SUNDOWN "sundown"
+/// after the 45 second wait, for actions that must go first
#define COMSIG_MAFIA_NIGHT_START "night_start"
+/// most night actions now resolve
#define COMSIG_MAFIA_NIGHT_ACTION_PHASE "night_actions"
+/// now killing happens from the roles that do that. the reason this is post action phase is to ensure doctors can protect and lawyers can block
#define COMSIG_MAFIA_NIGHT_KILL_PHASE "night_kill"
+/// now undoing states like protection, actions that must happen last, etc. right before shutters raise and the day begins
#define COMSIG_MAFIA_NIGHT_END "night_end"
+/// signal sent to roles when the game is confirmed ending
#define COMSIG_MAFIA_GAME_END "game_end"
-//list of ghosts who want to play mafia, every time someone enters the list it checks to see if enough are in
+/// list of ghosts who want to play mafia, every time someone enters the list it checks to see if enough are in
GLOBAL_LIST_EMPTY(mafia_signup)
-//the current global mafia game running.
+/// the current global mafia game running.
GLOBAL_VAR(mafia_game)
-
-GLOBAL_LIST_INIT(mafia_setups,generate_mafia_setups())
-
-/proc/generate_mafia_setups()
- . = list()
- for(var/T in subtypesof(/datum/mafia_setup))
- var/datum/mafia_setup/N = new T
- . += list(N.roles)
-
-/datum/mafia_setup
- var/name = "Make subtypes with the list and a name, more readable than list(list(),list()) etc"
- var/list/roles
-
-// 12 Player
-
-/datum/mafia_setup/twelve_basic
- name = "12 Player Setup Basic"
- roles = list(
- /datum/mafia_role=6,
- /datum/mafia_role/md=1,
- /datum/mafia_role/detective=1,
- /datum/mafia_role/clown=1,
- /datum/mafia_role/mafia=3
- )
-
-/datum/mafia_setup/twelve_md
- name = "12 Player Setup MD"
- roles = list(
- /datum/mafia_role=6,
- /datum/mafia_role/md=3,
- /datum/mafia_role/mafia=3
- )
-
-/datum/mafia_setup/twelve_all
- name = "12 Player Setup All"
- roles = list(
- /datum/mafia_role=1,
- /datum/mafia_role/psychologist=1,
- /datum/mafia_role/md=1,
- /datum/mafia_role/detective=1,
- /datum/mafia_role/clown=1,
- /datum/mafia_role/chaplain=1,
- /datum/mafia_role/lawyer=1,
- /datum/mafia_role/traitor=1,
- /datum/mafia_role/mafia=3,
- /datum/mafia_role/fugitive=1,
- /datum/mafia_role/obsessed=1
- )
-
-/datum/mafia_setup/twelve_joke
- name = "12 Player Setup Funny"
- roles = list(
- /datum/mafia_role=5,
- /datum/mafia_role/detective=2,
- /datum/mafia_role/clown=2,
- /datum/mafia_role/mafia=3
- )
-
-/datum/mafia_setup/twelve_lockdown
- name = "12 Player Setup Lockdown"
- roles = list(
- /datum/mafia_role=5,
- /datum/mafia_role/md=1,
- /datum/mafia_role/detective=1,
- /datum/mafia_role/lawyer=2,
- /datum/mafia_role/mafia=3
- )
-
-/datum/mafia_setup/twelve_rip
- name = "12 Player Setup RIP"
- roles = list(
- /datum/mafia_role=6,
- /datum/mafia_role/md=1,
- /datum/mafia_role/detective=1,
- /datum/mafia_role/mafia=3,
- /datum/mafia_role/traitor=1
- )
-
-/datum/mafia_setup/twelve_double_treason
- name = "12 Player Setup Double Treason"
- roles = list(
- /datum/mafia_role=8,
- /datum/mafia_role/detective=1,
- /datum/mafia_role/traitor=1,
- /datum/mafia_role/obsessed=2
- )
-
-/datum/mafia_setup/twelve_fugitives
- name = "12 Player Fugitives"
- roles = list(
- /datum/mafia_role=6,
- /datum/mafia_role/psychologist=1,
- /datum/mafia_role/mafia=3,
- /datum/mafia_role/fugitive=2
- )
-
-/datum/mafia_setup/twelve_traitor_mafia
- name = "12 Player Traitor Mafia"
- roles = list(
- /datum/mafia_role=3,
- /datum/mafia_role/psychologist=2,
- /datum/mafia_role/md=2,
- /datum/mafia_role/detective=2,
- /datum/mafia_role/traitor=3
- )
-
-/*
-/datum/mafia_setup/three_test
- name = "3 Player Test"
- roles = list(
- /datum/mafia_role/chaplain=1,
- /datum/mafia_role/psychologist=1,
- /datum/mafia_role/mafia=1
- )
-*/
diff --git a/code/modules/mafia/controller.dm b/code/modules/mafia/controller.dm
index 1e6ad963d35..793716848f5 100644
--- a/code/modules/mafia/controller.dm
+++ b/code/modules/mafia/controller.dm
@@ -13,7 +13,8 @@
var/phase = MAFIA_PHASE_SETUP
///how long the game has gone on for, changes with every sunrise. day one, night one, day two, etc.
var/turn = 0
-
+ ///for debugging and testing a full game, or adminbuse. If this is not null, it will use this as a setup. clears when game is over
+ var/list/custom_setup = list()
///first day has no voting, and thus is shorter
var/first_day_phase_period = 20 SECONDS
///talk with others about the last night
@@ -211,6 +212,7 @@
* * If the accused is killed, their true role is revealed to the rest of the players.
*/
/datum/mafia_controller/proc/lynch()
+
for(var/i in judgement_innocent_votes)
var/datum/mafia_role/role = i
send_message("[role.body.real_name] voted innocent.")
@@ -264,12 +266,12 @@
if(R.game_status == MAFIA_ALIVE)
switch(R.team)
if(MAFIA_TEAM_MAFIA)
- alive_mafia++
+ alive_mafia += R.vote_power
if(MAFIA_TEAM_TOWN)
- alive_town++
+ alive_town += R.vote_power
if(MAFIA_TEAM_SOLO)
if(R.solo_counts_as_town)
- alive_town++
+ alive_town += R.vote_power
solos_to_ask += R
///PHASE TWO: SEND STATS TO SOLO ANTAGS, SEE IF THEY WON OR TEAMS CANNOT WIN
@@ -324,8 +326,13 @@
map_deleter.generate() //remove the map, it will be loaded at the start of the next one
QDEL_LIST(all_roles)
+ current_setup_text = null
+ custom_setup = list()
turn = 0
votes = list()
+ //map gen does not deal with landmarks
+ QDEL_LIST(landmarks)
+ QDEL_NULL(town_center_landmark)
phase = MAFIA_PHASE_SETUP
/**
@@ -400,17 +407,17 @@
* Arguments:
* * voter: the mafia role that is trying to vote for...
* * target: the mafia role that is getting voted for
- * * vt: type of vote submitted (is this the day vote? is this the mafia night vote?)
+ * * vote_type: type of vote submitted (is this the day vote? is this the mafia night vote?)
* * teams: see mafia team defines for what to put in, makes the messages only send to a specific team (so mafia night votes only sending messages to mafia at night)
*/
-/datum/mafia_controller/proc/vote_for(datum/mafia_role/voter,datum/mafia_role/target,vt, teams)
- if(!votes[vt])
- votes[vt] = list()
- var/old_vote = votes[vt][voter]
+/datum/mafia_controller/proc/vote_for(datum/mafia_role/voter,datum/mafia_role/target,vote_type, teams)
+ if(!votes[vote_type])
+ votes[vote_type] = list()
+ var/old_vote = votes[vote_type][voter]
if(old_vote && old_vote == target)
- votes[vt] -= voter
+ votes[vote_type] -= voter
else
- votes[vt][voter] = target
+ votes[vote_type][voter] = target
if(old_vote && old_vote == target)
send_message("[voter.body.real_name] retracts their vote for [target.body.real_name]!", team = teams)
else
@@ -424,12 +431,12 @@
/**
* Clears out the votes of a certain type (day votes, mafia kill votes) while leaving others untouched
*/
-/datum/mafia_controller/proc/reset_votes(vt)
+/datum/mafia_controller/proc/reset_votes(vote_type)
var/list/bodies_to_update = list()
- for(var/vote in votes[vt])
- var/datum/mafia_role/R = votes[vt][vote]
+ for(var/vote in votes[vote_type])
+ var/datum/mafia_role/R = votes[vote_type][vote]
bodies_to_update += R.body
- votes[vt] = list()
+ votes[vote_type] = list()
for(var/mob/M in bodies_to_update)
M.update_icon()
@@ -437,38 +444,39 @@
* Returns how many people voted for the role, in whatever vote (day vote, night kill vote)
* Arguments:
* * role: the mafia role the proc tries to get the amount of votes for
- * * vt: the vote type (getting how many day votes were for the role, or mafia night votes for the role)
+ * * vote_type: the vote type (getting how many day votes were for the role, or mafia night votes for the role)
*/
-/datum/mafia_controller/proc/get_vote_count(role,vt)
+/datum/mafia_controller/proc/get_vote_count(role,vote_type)
. = 0
- for(var/votee in votes[vt])
- if(votes[vt][votee] == role)
- . += 1
+ for(var/v in votes[vote_type])
+ var/datum/mafia_role/votee = v
+ if(votes[vote_type][votee] == role)
+ . += votee.vote_power
/**
* Returns whichever role got the most votes, in whatever vote (day vote, night kill vote)
* returns null if no votes
* Arguments:
- * * vt: the vote type (getting the role that got the most day votes, or the role that got the most mafia votes)
+ * * vote_type: the vote type (getting the role that got the most day votes, or the role that got the most mafia votes)
*/
-/datum/mafia_controller/proc/get_vote_winner(vt)
+/datum/mafia_controller/proc/get_vote_winner(vote_type)
var/list/tally = list()
- for(var/votee in votes[vt])
- if(!tally[votes[vt][votee]])
- tally[votes[vt][votee]] = 1
+ for(var/votee in votes[vote_type])
+ if(!tally[votes[vote_type][votee]])
+ tally[votes[vote_type][votee]] = 1
else
- tally[votes[vt][votee]] += 1
+ tally[votes[vote_type][votee]] += 1
sortTim(tally,/proc/cmp_numeric_dsc,associative=TRUE)
return length(tally) ? tally[1] : null
/**
* Returns a random person who voted for whatever vote (day vote, night kill vote)
* Arguments:
- * * vt: vote type (getting a random day voter, or mafia night voter)
+ * * vote_type: vote type (getting a random day voter, or mafia night voter)
*/
-/datum/mafia_controller/proc/get_random_voter(vt)
- if(length(votes[vt]))
- return pick(votes[vt])
+/datum/mafia_controller/proc/get_random_voter(vote_type)
+ if(length(votes[vote_type]))
+ return pick(votes[vote_type])
/**
* Adds mutable appearances to people who get publicly voted on (so not night votes) showing how many people are picking them
@@ -573,9 +581,6 @@
basic_setup()
if("nuke")
end_game()
- for(var/i in landmarks)
- qdel(i)
- qdel(town_center_landmark)
qdel(src)
if("next_phase")
var/datum/timedevent/timer = SStimer.timer_id_dict[next_phase_timer]
@@ -592,10 +597,32 @@
continue
player.body.forceMove(get_turf(player.assigned_landmark))
if(failed.len)
- to_chat(usr, "List of players who no longer had a body (if you see this, the game is runtiming anyway so just hit \"New Game\" to end it")
+ to_chat(usr, "List of players who no longer had a body (if you see this, the game is runtiming anyway so just hit \"New Game\" to end it)")
for(var/i in failed)
var/datum/mafia_role/fail = i
to_chat(usr, fail.player_key)
+ if("debug_setup")
+ var/list/debug_setup = list()
+ var/list/rolelist_dict = list()
+ var/done = FALSE
+ for(var/p in typesof(/datum/mafia_role))
+ var/datum/mafia_role/path = p
+ rolelist_dict[initial(path.name) + " ([uppertext(initial(path.team))])"] = path
+ rolelist_dict = list("CANCEL", "FINISH") + rolelist_dict
+ while(!done)
+ to_chat(usr, "You have a total player count of [assoc_value_sum(debug_setup)] in this setup.")
+ var/chosen_role_name = input(usr,"Select a role!","Custom Setup Creation",rolelist_dict[1]) as null|anything in rolelist_dict
+ if(chosen_role_name == "CANCEL")
+ return
+ if(chosen_role_name == "FINISH")
+ break
+ var/found_path = rolelist_dict[chosen_role_name]
+ var/role_count = input(usr,"How many? Zero to cancel.","Custom Setup Creation",0) as null|num
+ if(role_count > 0)
+ debug_setup[found_path] = role_count
+ custom_setup = debug_setup
+ if("cancel_setup")
+ custom_setup = list()
switch(action)
if("mf_lookup")
var/role_lookup = params["atype"]
@@ -623,7 +650,7 @@
if("Vote")
if(phase != MAFIA_PHASE_VOTING)
return
- vote_for(user_role,target,vt="Day")
+ vote_for(user_role,target,vote_type="Day")
if("Kill Vote")
if(phase != MAFIA_PHASE_NIGHT || user_role.team != MAFIA_TEAM_MAFIA)
return
@@ -667,31 +694,87 @@
. += L[key]
/**
- * Returns all setups that the amount of players signed up could support (so fill each role)
- * Arguments:
- * * ready_count: the amount of players signed up (not sane, so some players may have disconnected or rejoined ss13).
+ * Returns a semirandom setup, with...
+ * Town, Two invest roles, one protect role, sometimes a misc role, and the rest assistants for town.
+ * Mafia, 2 normal mafia and one special.
+ * Neutral, two disruption roles, sometimes one is a killing.
+ *
+ * See _defines.dm in the mafia folder for a rundown on what these groups of roles include.
*/
-/datum/mafia_controller/proc/find_best_setup(ready_count)
- var/list/all_setups = GLOB.mafia_setups
- var/valid_setups = list()
- for(var/S in all_setups)
- var/req_players = assoc_value_sum(S)
- if(req_players <= ready_count)
- valid_setups += list(S)
- return length(valid_setups) > 0 ? pick(valid_setups) : null
+/datum/mafia_controller/proc/generate_random_setup()
+ var/invests_left = 2
+ var/protects_left = 1
+ var/miscs_left = prob(35)
+ var/mafiareg_left = 2
+ var/mafiaspe_left = 1
+ var/killing_role = prob(50)
+ var/disruptors = killing_role ? 1 : 2 //still required to calculate overflow
+ var/overflow_left = MAFIA_MAX_PLAYER_COUNT - (invests_left + protects_left + miscs_left + mafiareg_left + mafiaspe_left + killing_role + disruptors)
+
+ var/list/random_setup = list()
+ for(var/i in 1 to MAFIA_MAX_PLAYER_COUNT) //should match the number of roles to add
+ if(overflow_left)
+ add_setup_role(random_setup, TOWN_OVERFLOW)
+ overflow_left--
+ else if(invests_left)
+ add_setup_role(random_setup, TOWN_INVEST)
+ invests_left--
+ else if(protects_left)
+ add_setup_role(random_setup, TOWN_PROTECT)
+ protects_left--
+ else if(miscs_left)
+ add_setup_role(random_setup, TOWN_MISC)
+ miscs_left--
+ else if(mafiareg_left)
+ add_setup_role(random_setup, MAFIA_REGULAR)
+ mafiareg_left--
+ else if(mafiaspe_left)
+ add_setup_role(random_setup, MAFIA_SPECIAL)
+ mafiaspe_left--
+ else if(killing_role)
+ add_setup_role(random_setup, NEUTRAL_KILL)
+ killing_role--
+ else
+ add_setup_role(random_setup, NEUTRAL_DISRUPT)
+ return random_setup
+
+/**
+ * Helper proc that adds a random role of a type to a setup. if it doesn't exist in the setup, it adds the path to the list and otherwise bumps the path in the list up one
+ */
+/datum/mafia_controller/proc/add_setup_role(setup_list, wanted_role_type)
+ var/list/role_type_paths = list()
+ for(var/path in typesof(/datum/mafia_role))
+ var/datum/mafia_role/instance = path
+ if(initial(instance.role_type) == wanted_role_type)
+ role_type_paths += instance
+
+ var/mafia_path = pick(role_type_paths)
+ var/datum/mafia_role/mafia_path_type = mafia_path
+ var/found_role
+ for(var/searched_path in setup_list)
+ var/datum/mafia_role/searched_path_type = searched_path
+ if(initial(mafia_path_type.name) == initial(searched_path_type.name))
+ found_role = searched_path
+ break
+ if(found_role)
+ setup_list[found_role] += 1
+ return
+ setup_list[mafia_path] = 1
/**
* Called when enough players have signed up to fill a setup. DOESN'T NECESSARILY MEAN THE GAME WILL START.
*
+ * Checks for a custom setup, if so gets the required players from that and if not it sets the player requirement to MAFIA_MAX_PLAYER_COUNT and generates one IF basic setup starts a game.
* Checks if everyone signed up is an observer, and is still connected. If people aren't, they're removed from the list.
* If there aren't enough players post sanity, it aborts. otherwise, it selects enough people for the game and starts preparing the game for real.
*/
/datum/mafia_controller/proc/basic_setup()
- var/ready_count = length(GLOB.mafia_signup)
- var/list/setup = find_best_setup(ready_count)
- if(!setup)
- return
- var/req_players = assoc_value_sum(setup) //12
+ var/req_players
+ var/list/setup = custom_setup
+ if(!setup.len)
+ req_players = MAFIA_MAX_PLAYER_COUNT
+ else
+ req_players = assoc_value_sum(setup)
//final list for all the players who will be in this game
var/list/filtered_keys = list()
@@ -719,8 +802,10 @@
for(var/unpicked in possible_keys)
var/client/unpicked_client = GLOB.directory[unpicked]
to_chat(unpicked_client, "Sorry, the starting mafia game has too many players and you were not picked.")
- to_chat(unpicked_client, "You're still signed up, and have another chance to join when the one starting now finishes.")
+ to_chat(unpicked_client, "You're still signed up, getting messages from the current round, and have another chance to join when the one starting now finishes.")
+ if(!setup.len) //don't actually have one yet, so generate a max player random setup. it's good to do this here instead of above so it doesn't generate one every time a game could possibly start.
+ setup = generate_random_setup()
prepare_game(setup,filtered_keys)
start_game()
@@ -732,10 +817,7 @@
/datum/mafia_controller/proc/try_autostart()
if(phase != MAFIA_PHASE_SETUP)
return
- var/min_players = INFINITY // fairly sure mmo mafia is not a thing and i'm lazy
- for(var/setup in GLOB.mafia_setups)
- min_players = min(min_players,assoc_value_sum(setup))
- if(GLOB.mafia_signup.len >= min_players)//enough people to try and make something
+ if(GLOB.mafia_signup.len >= MAFIA_MAX_PLAYER_COUNT || custom_setup.len)//enough people to try and make something (or debug mode)
basic_setup()
/datum/action/innate/mafia_panel
diff --git a/code/modules/mafia/map_pieces.dm b/code/modules/mafia/map_pieces.dm
index f0472c7e9f0..028587ffdba 100644
--- a/code/modules/mafia/map_pieces.dm
+++ b/code/modules/mafia/map_pieces.dm
@@ -14,11 +14,14 @@
name = "Mafia Game Board"
icon = 'icons/obj/mafia.dmi'
icon_state = "board"
+ anchored = TRUE
var/game_id = "mafia"
+ var/datum/mafia_controller/MF
/obj/mafia_game_board/attack_ghost(mob/user)
. = ..()
- var/datum/mafia_controller/MF = GLOB.mafia_game
+ if(!MF)
+ MF = GLOB.mafia_game
if(!MF)
MF = create_mafia_game()
MF.ui_interact(user)
diff --git a/code/modules/mafia/outfits.dm b/code/modules/mafia/outfits.dm
index 65c6b9e9803..3a566196460 100644
--- a/code/modules/mafia/outfits.dm
+++ b/code/modules/mafia/outfits.dm
@@ -42,13 +42,6 @@
uniform = /obj/item/clothing/under/rank/civilian/chaplain
-/datum/outfit/mafia/clown
- name = "Mafia Clown"
-
- uniform = /obj/item/clothing/under/rank/civilian/clown
- shoes = /obj/item/clothing/shoes/clown_shoes
- mask = /obj/item/clothing/mask/gas/clown_hat
-
/datum/outfit/mafia/lawyer
name = "Mafia Lawyer"
@@ -56,8 +49,14 @@
suit = /obj/item/clothing/suit/toggle/lawyer
shoes = /obj/item/clothing/shoes/laceup
+/datum/outfit/mafia/hop
+ name = "Mafia Head of Personnel"
-
+ uniform = /obj/item/clothing/under/rank/civilian/head_of_personnel
+ suit = /obj/item/clothing/suit/armor/vest/alt
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ head = /obj/item/clothing/head/hopcap
+ glasses = /obj/item/clothing/glasses/sunglasses
//mafia
@@ -88,9 +87,22 @@
carried_item.add_mob_blood(H)//Oh yes, there will be blood...
H.regenerate_icons()
+/datum/outfit/mafia/clown
+ name = "Mafia Clown"
+
+ uniform = /obj/item/clothing/under/rank/civilian/clown
+ shoes = /obj/item/clothing/shoes/clown_shoes
+ mask = /obj/item/clothing/mask/gas/clown_hat
+
/datum/outfit/mafia/traitor
name = "Mafia Traitor"
mask = /obj/item/clothing/mask/gas/syndicate
uniform = /obj/item/clothing/under/syndicate/tacticool
shoes = /obj/item/clothing/shoes/jackboots
+
+/datum/outfit/mafia/nightmare
+ name = "Mafia Nightmare"
+
+ uniform = null
+ shoes = null
diff --git a/code/modules/mafia/roles.dm b/code/modules/mafia/roles.dm
index 394d121005d..e1f0c0143a7 100644
--- a/code/modules/mafia/roles.dm
+++ b/code/modules/mafia/roles.dm
@@ -3,11 +3,16 @@
var/desc = "You are a crewmember without any special abilities."
var/win_condition = "kill all mafia and solo killing roles."
var/team = MAFIA_TEAM_TOWN
+ ///how the random setup chooses which roles get put in
+ var/role_type = TOWN_OVERFLOW
var/player_key
var/mob/living/carbon/human/body
var/obj/effect/landmark/mafia/assigned_landmark
+ ///how many votes submitted when you vote.
+ var/vote_power = 1
+ var/detect_immune = FALSE
var/revealed = FALSE
var/datum/outfit/revealed_outfit = /datum/outfit/mafia/assistant //the assistants need a special path to call out they were in fact assistant, everything else can just use job equipment
//action = uses
@@ -53,7 +58,6 @@
to_chat(body,"You are not aligned to town or mafia. Accomplish your own objectives!")
to_chat(body, "Be sure to read the wiki page to learn more, if you have no idea what's going on.")
-//please take care with this, they can break shit with their equipment unless you specifically disallow them (aka stun at the end of the game)
/datum/mafia_role/proc/reveal_role(datum/mafia_controller/game, verbose = FALSE)
if(revealed)
return
@@ -62,9 +66,13 @@
var/list/oldoutfit = body.get_equipped_items()
for(var/thing in oldoutfit)
qdel(thing)
+ special_reveal_equip(game)
body.equipOutfit(revealed_outfit)
revealed = TRUE
+/datum/mafia_role/proc/special_reveal_equip(datum/mafia_controller/game)
+ return
+
/datum/mafia_role/proc/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target)
return
@@ -107,6 +115,7 @@
name = "Detective"
desc = "You can investigate a single person each night to learn their team."
revealed_outfit = /datum/outfit/mafia/detective
+ role_type = TOWN_INVEST
targeted_actions = list("Investigate")
@@ -130,28 +139,97 @@
current_investigation = target
/datum/mafia_role/detective/proc/investigate(datum/mafia_controller/game)
- var/datum/mafia_role/R = current_investigation
- if(R)
- var/team_text
- var/fluff
- switch(R.team)
- if(MAFIA_TEAM_TOWN)
- team_text = "Town"
- fluff = "a true member of the station."
- if(MAFIA_TEAM_MAFIA)
- team_text = "Mafia"
- fluff = "an unfeeling, hideous changeling!"
- if(MAFIA_TEAM_SOLO)
- team_text = "Solo"
- fluff = "a rogue, with their own objectives..."
- to_chat(body,"Your investigations reveal that [R.body.real_name] is [fluff]")
- add_note("N[game.turn] - [R.body.real_name] - [team_text]")
+ var/datum/mafia_role/target = current_investigation
+ if(target)
+ if(target.detect_immune)
+ to_chat(body,"Your investigations reveal that [target.body.real_name] is a true member of the station.")
+ add_note("N[game.turn] - [target.body.real_name] - Town")
+ else
+ var/team_text
+ var/fluff
+ switch(target.team)
+ if(MAFIA_TEAM_TOWN)
+ team_text = "Town"
+ fluff = "a true member of the station."
+ if(MAFIA_TEAM_MAFIA)
+ team_text = "Mafia"
+ fluff = "an unfeeling, hideous changeling!"
+ if(MAFIA_TEAM_SOLO)
+ team_text = "Solo"
+ fluff = "a rogue, with their own objectives..."
+ to_chat(body,"Your investigations reveal that [target.body.real_name] is [fluff]")
+ add_note("N[game.turn] - [target.body.real_name] - [team_text]")
current_investigation = null
+/datum/mafia_role/psychologist
+ name = "Psychologist"
+ desc = "You can visit someone ONCE PER GAME to reveal their true role in the morning!"
+ revealed_outfit = /datum/outfit/mafia/psychologist
+ role_type = TOWN_INVEST
+
+ targeted_actions = list("Reveal")
+ var/datum/mafia_role/current_target
+ var/can_use = TRUE
+
+/datum/mafia_role/psychologist/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/therapy_reveal)
+
+/datum/mafia_role/psychologist/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!. || !can_use || game.phase == MAFIA_PHASE_NIGHT || target.game_status != MAFIA_ALIVE || target.revealed || target == src)
+ return FALSE
+
+/datum/mafia_role/psychologist/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ to_chat(body,"You will reveal [target.body.real_name] tonight.")
+ current_target = target
+
+/datum/mafia_role/psychologist/proc/therapy_reveal(datum/mafia_controller/game)
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"reveal",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by a lawyer.
+ current_target = null
+ if(current_target)
+ add_note("N[game.turn] - [current_target.body.real_name] - Revealed true identity")
+ to_chat(body,"You have revealed the true nature of the [current_target]!")
+ current_target.reveal_role(game, verbose = TRUE)
+ current_target = null
+ can_use = FALSE
+
+/datum/mafia_role/chaplain
+ name = "Chaplain"
+ desc = "You can communicate with spirits of the dead each night to discover dead crewmember roles."
+ revealed_outfit = /datum/outfit/mafia/chaplain
+ role_type = TOWN_INVEST
+
+ targeted_actions = list("Pray")
+ var/current_target
+
+/datum/mafia_role/chaplain/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_ACTION_PHASE,.proc/commune)
+
+/datum/mafia_role/chaplain/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!.)
+ return
+ return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_DEAD && target != src && !target.revealed
+
+/datum/mafia_role/chaplain/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ to_chat(body,"You will commune with the spirit of [target.body.real_name] tonight.")
+ current_target = target
+
+/datum/mafia_role/chaplain/proc/commune(datum/mafia_controller/game)
+ var/datum/mafia_role/target = current_target
+ if(target)
+ to_chat(body,"You invoke spirit of [target.body.real_name] and learn their role was [target.name].")
+ add_note("N[game.turn] - [target.body.real_name] - [target.name]")
+ current_target = null
+
/datum/mafia_role/md
name = "Medical Doctor"
desc = "You can protect a single person each night from killing."
revealed_outfit = /datum/outfit/mafia/md // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these)
+ role_type = TOWN_PROTECT
targeted_actions = list("Protect")
@@ -166,6 +244,8 @@
. = ..()
if(!.)
return
+ if(target.name == "Head of Personnel" && target.revealed)
+ return FALSE
return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_ALIVE && target != src
/datum/mafia_role/md/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target)
@@ -190,40 +270,12 @@
UnregisterSignal(current_protected,COMSIG_MAFIA_ON_KILL)
current_protected = null
-/datum/mafia_role/chaplain
- name = "Chaplain"
- desc = "You can communicate with spirits of the dead each night to discover dead crewmember roles."
- revealed_outfit = /datum/outfit/mafia/chaplain
-
- targeted_actions = list("Pray")
- var/current_target
-
-/datum/mafia_role/chaplain/New(datum/mafia_controller/game)
- . = ..()
- RegisterSignal(game,COMSIG_MAFIA_NIGHT_ACTION_PHASE,.proc/commune)
-
-/datum/mafia_role/chaplain/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
- . = ..()
- if(!.)
- return
- return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_DEAD && target != src && !target.revealed
-
-/datum/mafia_role/chaplain/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
- to_chat(body,"You will commune with the spirit of [target.body.real_name] tonight.")
- current_target = target
-
-/datum/mafia_role/chaplain/proc/commune(datum/mafia_controller/game)
- var/datum/mafia_role/R = current_target
- if(R)
- to_chat(body,"You invoke spirit of [R.body.real_name] and learn their role was [R.name].")
- add_note("N[game.turn] - [R.body.real_name] - [R.name]")
- current_target = null
-
/datum/mafia_role/lawyer
name = "Lawyer"
desc = "You can choose a person during the day to provide extensive legal advice to during the night, preventing night actions."
-
revealed_outfit = /datum/outfit/mafia/lawyer
+ role_type = TOWN_PROTECT
+
targeted_actions = list("Advise")
var/datum/mafia_role/current_target
@@ -273,38 +325,23 @@
if(game_status == MAFIA_ALIVE) //in case we got killed while imprisoning sk - bad luck edge
return MAFIA_PREVENT_ACTION
-/datum/mafia_role/psychologist
- name = "Psychologist"
- desc = "You can visit someone ONCE PER GAME to reveal their true role in the morning!"
- revealed_outfit = /datum/outfit/mafia/psychologist
+/datum/mafia_role/hop
+ name = "Head of Personnel"
+ desc = "You can reveal yourself once per game, tripling your vote power but becoming unable to be protected!"
+ revealed_outfit = /datum/outfit/mafia/hop
+ role_type = TOWN_MISC
targeted_actions = list("Reveal")
- var/datum/mafia_role/current_target
- var/can_use = TRUE
-/datum/mafia_role/psychologist/New(datum/mafia_controller/game)
+/datum/mafia_role/hop/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
. = ..()
- RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/therapy_reveal)
-
-/datum/mafia_role/psychologist/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
- . = ..()
- if(!. || !can_use || game.phase == MAFIA_PHASE_NIGHT || target.game_status != MAFIA_ALIVE || target.revealed || target == src)
+ if(!. || game.phase == MAFIA_PHASE_NIGHT || game.turn == 1 || target.game_status != MAFIA_ALIVE || target != src || revealed)
return FALSE
-/datum/mafia_role/psychologist/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+/datum/mafia_role/hop/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
. = ..()
- to_chat(body,"You will reveal [target.body.real_name] tonight.")
- current_target = target
-
-/datum/mafia_role/psychologist/proc/therapy_reveal(datum/mafia_controller/game)
- if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"reveal",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by a lawyer.
- current_target = null
- if(current_target)
- add_note("N[game.turn] - [current_target.body.real_name] - Revealed true identity")
- to_chat(body,"You have revealed the true nature of the [current_target]!")
- current_target.reveal_role(game, verbose = TRUE)
- current_target = null
- can_use = FALSE
+ reveal_role(game, TRUE)
+ vote_power = 2
///MAFIA ROLES/// only one until i rework this to allow more, they're the "anti-town" working to kill off townies to win
@@ -312,6 +349,7 @@
name = "Changeling"
desc = "You're a member of the changeling hive. Use ':j' talk prefix to talk to your fellow lings."
team = MAFIA_TEAM_MAFIA
+ role_type = MAFIA_REGULAR
revealed_outfit = /datum/outfit/mafia/changeling
special_theme = "syndicate"
win_condition = "become majority over the town and no solo killing role can stop them."
@@ -323,6 +361,45 @@
/datum/mafia_role/mafia/proc/mafia_text(datum/mafia_controller/source)
to_chat(body,"Vote for who to kill tonight. The killer will be chosen randomly from voters.")
+//better detective for mafia
+/datum/mafia_role/mafia/thoughtfeeder
+ name = "Thoughtfeeder"
+ desc = "You're a changeling variant that feeds on the memories of others. Use ':j' talk prefix to talk to your fellow lings, and visit people at night to learn their role."
+ role_type = MAFIA_SPECIAL
+ targeted_actions = list("Learn Role")
+
+ var/datum/mafia_role/current_investigation
+
+/datum/mafia_role/mafia/thoughtfeeder/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_ACTION_PHASE,.proc/investigate)
+
+/datum/mafia_role/mafia/thoughtfeeder/validate_action_target(datum/mafia_controller/game,action,datum/mafia_role/target)
+ . = ..()
+ if(!.)
+ return
+ return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_ALIVE && target != src
+
+/datum/mafia_role/mafia/thoughtfeeder/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target)
+ to_chat(body,"You will feast on the memories of [target.body.real_name] tonight.")
+ current_investigation = target
+
+/datum/mafia_role/mafia/thoughtfeeder/proc/investigate(datum/mafia_controller/game)
+ var/datum/mafia_role/target = current_investigation
+ current_investigation = null
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"thoughtfeed",target) & MAFIA_PREVENT_ACTION)
+ to_chat(body,"You were unable to investigate [target.body.real_name].")
+ add_note("N[game.turn] - [target.body.real_name] - Unable to investigate")
+ return
+ if(target)
+ if(target.detect_immune)
+ to_chat(body,"[target.body.real_name]'s memories reveal that they are the Assistant.")
+ add_note("N[game.turn] - [target.body.real_name] - Assistant")
+ else
+ to_chat(body,"[target.body.real_name]'s memories reveal that they are the [target.name].")
+ add_note("N[game.turn] - [target.body.real_name] - [target.name]")
+
+
///SOLO ROLES/// they range from anomalous factors to deranged killers that try to win alone.
/datum/mafia_role/traitor
@@ -330,6 +407,7 @@
desc = "You're a solo traitor. You are immune to night kills, can kill every night and you win by outnumbering everyone else."
win_condition = "kill everyone."
team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_KILL
targeted_actions = list("Night Kill")
revealed_outfit = /datum/outfit/mafia/traitor
special_theme = "syndicate"
@@ -365,10 +443,80 @@
to_chat(body,"You will attempt to kill [target.body.real_name] tonight.")
/datum/mafia_role/traitor/proc/try_to_kill(datum/mafia_controller/source)
- if(game_status == MAFIA_ALIVE && current_victim && current_victim.game_status == MAFIA_ALIVE)
- if(!current_victim.kill(source))
- to_chat(body,"Your attempt at killing [current_victim.body] was prevented!")
+ var/datum/mafia_role/target = current_victim
current_victim = null
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"traitor kill",target) & MAFIA_PREVENT_ACTION)
+ return
+ if(game_status == MAFIA_ALIVE && target && target.game_status == MAFIA_ALIVE)
+ if(!target.kill(source))
+ to_chat(body,"Your attempt at killing [target.body] was prevented!")
+
+/datum/mafia_role/nightmare
+ name = "Nightmare"
+ desc = "You're a solo monster that cannot be detected by detective roles. You can flicker lights of another room each night. You can instead decide to hunt, killing everyone in a flickering room. Kill everyone to win."
+ win_condition = "kill everyone."
+ revealed_outfit = /datum/outfit/mafia/nightmare
+ detect_immune = TRUE
+ team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_KILL
+ targeted_actions = list("Flicker", "Hunt")
+ var/list/flickering = list()
+ var/datum/mafia_role/flicker_target
+
+/datum/mafia_role/nightmare/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_KILL_PHASE,.proc/flicker_or_hunt)
+
+/datum/mafia_role/nightmare/check_total_victory(alive_town, alive_mafia) //nightmares just want teams dead
+ return alive_town + alive_mafia <= 1
+
+/datum/mafia_role/nightmare/block_team_victory(alive_town, alive_mafia) //no team can win until they're dead
+ return TRUE //while alive, town AND mafia cannot win (though since mafia know who is who it's pretty easy to win from that point)
+
+/datum/mafia_role/nightmare/special_reveal_equip()
+ body.underwear = "Nude"
+ body.undershirt = "Nude"
+ body.socks = "Nude"
+ body.set_species(/datum/species/shadow)
+ body.update_body()
+
+/datum/mafia_role/nightmare/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!. || game.phase != MAFIA_PHASE_NIGHT || target.game_status != MAFIA_ALIVE)
+ return FALSE
+ if(action == "Flicker")
+ return target != src && !(target in flickering)
+ return target == src
+
+/datum/mafia_role/nightmare/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(target == flicker_target)
+ to_chat(body,"You will do nothing tonight.")
+ flicker_target = null
+ flicker_target = target
+ if(action == "Flicker")
+ to_chat(body,"You will attempt to flicker [target.body.real_name]'s room tonight.")
+ else
+ to_chat(body,"You will hunt everyone in a flickering room down tonight.")
+
+/datum/mafia_role/nightmare/proc/flicker_or_hunt(datum/mafia_controller/source)
+ if(game_status != MAFIA_ALIVE || !flicker_target)
+ return
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"nightmare actions",flicker_target) & MAFIA_PREVENT_ACTION)
+ to_chat(flicker_target.body, "Your actions were prevented!")
+ return
+ var/datum/mafia_role/target = flicker_target
+ flicker_target = null
+ if(target != src) //flicker instead of hunt
+ to_chat(target.body, "The lights begin to flicker and dim. You're in danger.")
+ flickering += target
+ return
+ for(var/r in flickering)
+ var/datum/mafia_role/role = r
+ if(role && role.game_status == MAFIA_ALIVE)
+ to_chat(role.body, "A shadowy monster appears out of the darkness!")
+ role.kill(source)
+ flickering -= role
//just helps read better
#define FUGITIVE_NOT_PRESERVING 0//will not become night immune tonight
@@ -379,6 +527,7 @@
desc = "You're on the run. You can become immune to night kills exactly twice, and you win by surviving to the end of the game with anyone."
win_condition = "survive to the end of the game, with anyone"
team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_DISRUPT
actions = list("Self Preservation")
var/charges = 2
var/protection_status = FUGITIVE_NOT_PRESERVING
@@ -433,6 +582,7 @@
desc = "You're completely lost in your own mind. You win by lynching your obsession before you get killed in this mess. Obsession assigned on the first night!"
win_condition = "lynch their obsession."
team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_DISRUPT
revealed_outfit = /datum/outfit/mafia/obsessed // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these)
solo_counts_as_town = TRUE //after winning or whatever, can side with whoever. they've already done their objective!
@@ -474,6 +624,7 @@
win_condition = "get themselves lynched!"
revealed_outfit = /datum/outfit/mafia/clown
team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_DISRUPT
/datum/mafia_role/clown/New(datum/mafia_controller/game)
. = ..()
diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm
index 7b17fa03820..17af647cc64 100644
--- a/code/modules/mining/abandoned_crates.dm
+++ b/code/modules/mining/abandoned_crates.dm
@@ -218,7 +218,7 @@
new /obj/item/banhammer(src)
for(var/i in 1 to 3)
var/obj/effect/mine/sound/bwoink/mine = new (src)
- mine.anchored = FALSE
+ mine.set_anchored(FALSE)
mine.move_resist = MOVE_RESIST_DEFAULT
if(97)
for(var/i in 1 to 4)
diff --git a/code/modules/mining/aux_base_camera.dm b/code/modules/mining/aux_base_camera.dm
index 12d7ae32b29..b013bc42e62 100644
--- a/code/modules/mining/aux_base_camera.dm
+++ b/code/modules/mining/aux_base_camera.dm
@@ -202,27 +202,27 @@
name = "Select Airlock Type"
button_icon_state = "airlock_select"
-datum/action/innate/aux_base/airlock_type/Activate()
+/datum/action/innate/aux_base/airlock_type/Activate()
if(..())
return
B.RCD.change_airlock_setting()
-datum/action/innate/aux_base/window_type
+/datum/action/innate/aux_base/window_type
name = "Select Window Type"
button_icon_state = "window_select"
-datum/action/innate/aux_base/window_type/Activate()
+/datum/action/innate/aux_base/window_type/Activate()
if(..())
return
B.RCD.toggle_window_type()
-datum/action/innate/aux_base/place_fan
+/datum/action/innate/aux_base/place_fan
name = "Place Tiny Fan"
button_icon_state = "build_fan"
-datum/action/innate/aux_base/place_fan/Activate()
+/datum/action/innate/aux_base/place_fan/Activate()
if(..())
return
@@ -244,11 +244,11 @@ datum/action/innate/aux_base/place_fan/Activate()
to_chat(owner, "Tiny fan placed. [B.fans_remaining] remaining.")
playsound(fan_turf, 'sound/machines/click.ogg', 50, TRUE)
-datum/action/innate/aux_base/install_turret
+/datum/action/innate/aux_base/install_turret
name = "Install Plasma Anti-Wildlife Turret"
button_icon_state = "build_turret"
-datum/action/innate/aux_base/install_turret/Activate()
+/datum/action/innate/aux_base/install_turret/Activate()
if(..())
return
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index 2553edeb394..d74088f09b0 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -17,7 +17,7 @@
custom_materials = list(/datum/material/iron=1150, /datum/material/glass=2075)
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("smashed", "crushed", "cleaved", "chopped", "pulped")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
actions_types = list(/datum/action/item_action/toggle_light)
obj_flags = UNIQUE_RENAME
var/list/trophies = list()
diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm
index a2c004a09dc..2ceb85d7362 100644
--- a/code/modules/mining/equipment/mining_tools.dm
+++ b/code/modules/mining/equipment/mining_tools.dm
@@ -120,6 +120,7 @@
desc = "A large tool for digging and moving dirt."
icon = 'icons/obj/mining.dmi'
icon_state = "shovel"
+ worn_icon_state = "shovel"
lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi'
flags_1 = CONDUCT_1
@@ -133,7 +134,7 @@
w_class = WEIGHT_CLASS_NORMAL
custom_materials = list(/datum/material/iron=50)
attack_verb = list("bashed", "bludgeoned", "thrashed", "whacked")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/shovel/Initialize()
. = ..()
@@ -151,6 +152,7 @@
desc = "A small tool for digging and moving dirt."
icon_state = "spade"
inhand_icon_state = "spade"
+ worn_icon_state = "spade"
lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi'
force = 5
@@ -162,6 +164,7 @@
desc = "A wicked tool that cleaves through dirt just as easily as it does flesh. The design was styled after ancient lavaland tribal designs."
icon_state = "shovel_bone"
inhand_icon_state = "shovel_bone"
+ worn_icon_state = "shovel_serr"
lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi'
force = 15
@@ -169,4 +172,4 @@
w_class = WEIGHT_CLASS_NORMAL
toolspeed = 0.7
attack_verb = list("slashed", "impaled", "stabbed", "sliced")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm
index b1610cad833..15b24cc0f07 100644
--- a/code/modules/mining/equipment/survival_pod.dm
+++ b/code/modules/mining/equipment/survival_pod.dm
@@ -87,7 +87,7 @@
name = "pod window"
icon = 'icons/obj/smooth_structures/pod_window.dmi'
icon_state = "smooth"
- smooth = SMOOTH_MORE
+ smoothing_flags = SMOOTH_MORE
canSmoothWith = list(/turf/closed/wall/mineral/titanium/survival, /obj/machinery/door/airlock/survival_pod, /obj/structure/window/shuttle/survival_pod)
/obj/structure/window/shuttle/survival_pod/spawner/north
@@ -133,7 +133,7 @@
/obj/structure/table/survival_pod
icon = 'icons/obj/lavaland/survival_pod.dmi'
icon_state = "table"
- smooth = SMOOTH_FALSE
+ smoothing_flags = NONE
//Sleeper
/obj/machinery/sleeper/survival_pod
@@ -197,22 +197,20 @@
desc = "A heated storage unit."
icon_state = "donkvendor"
icon = 'icons/obj/lavaland/donkvendor.dmi'
+ base_build_path = /obj/machinery/smartfridge/survival_pod
light_range = 5
light_power = 1.2
light_color = "#DDFFD3"
max_n_of_items = 10
pixel_y = -4
flags_1 = NODECONSTRUCT_1
- var/empty = FALSE
/obj/machinery/smartfridge/survival_pod/ComponentInitialize()
. = ..()
AddElement(/datum/element/update_icon_blocker)
-/obj/machinery/smartfridge/survival_pod/Initialize(mapload)
+/obj/machinery/smartfridge/survival_pod/preloaded/Initialize(mapload)
. = ..()
- if(empty)
- return
for(var/i in 1 to 5)
var/obj/item/reagent_containers/food/snacks/donkpocket/warm/W = new(src)
load(W)
@@ -226,11 +224,6 @@
/obj/machinery/smartfridge/survival_pod/accept_check(obj/item/O)
return isitem(O)
-/obj/machinery/smartfridge/survival_pod/empty
- name = "dusty survival pod storage"
- desc = "A heated storage unit. This one's seen better days."
- empty = TRUE
-
//Fans
/obj/structure/fans
icon = 'icons/obj/lavaland/survival_pod.dmi'
diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm
index ed3dd4fa207..1f970b121a0 100644
--- a/code/modules/mining/fulton.dm
+++ b/code/modules/mining/fulton.dm
@@ -81,7 +81,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
M.Paralyze(320) // Keep them from moving during the duration of the extraction
M.buckled = 0 // Unbuckle them to prevent anchoring problems
else
- A.anchored = TRUE
+ A.set_anchored(TRUE)
A.density = FALSE
var/obj/effect/extraction_holder/holder_obj = new(A.loc)
holder_obj.appearance = A.appearance
@@ -132,7 +132,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
holder_obj.add_overlay(balloon3)
sleep(4)
holder_obj.cut_overlay(balloon3)
- A.anchored = FALSE // An item has to be unanchored to be extracted in the first place.
+ A.set_anchored(FALSE) // An item has to be unanchored to be extracted in the first place.
A.density = initial(A.density)
animate(holder_obj, pixel_z = 0, time = 5)
sleep(5)
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 809796c8e3c..e1d2c332730 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -711,7 +711,7 @@
hitsound = 'sound/weapons/bladeslice.ogg'
hitsound_on = 'sound/weapons/bladeslice.ogg'
w_class = WEIGHT_CLASS_BULKY
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
faction_bonus_force = 30
nemesis_factions = list("mining", "boss")
var/transform_cooldown
@@ -817,7 +817,7 @@
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
flags_1 = CONDUCT_1
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
w_class = WEIGHT_CLASS_BULKY
force = 1
throwforce = 1
diff --git a/code/modules/mining/mint.dm b/code/modules/mining/mint.dm
deleted file mode 100644
index 82b2dbca6fb..00000000000
--- a/code/modules/mining/mint.dm
+++ /dev/null
@@ -1,145 +0,0 @@
-/**********************Mint**************************/
-
-
-/obj/machinery/mineral/mint
- name = "coin press"
- icon = 'icons/obj/economy.dmi'
- icon_state = "coinpress0"
- density = TRUE
- input_dir = EAST
- needs_item_input = TRUE
- var/obj/item/storage/bag/money/bag_to_use
- var/produced_coins = 0 // how many coins the machine has made in it's last cycle
- var/processing = FALSE
- var/chosen = /datum/material/iron //which material will be used to make coins
-
-
-/obj/machinery/mineral/mint/Initialize()
- . = ..()
- AddComponent(/datum/component/material_container, list(
- /datum/material/iron,
- /datum/material/plasma,
- /datum/material/silver,
- /datum/material/gold,
- /datum/material/uranium,
- /datum/material/titanium,
- /datum/material/diamond,
- /datum/material/bananium,
- /datum/material/adamantine,
- /datum/material/mythril,
- /datum/material/plastic,
- /datum/material/runite
- ), MINERAL_MATERIAL_AMOUNT * 75, FALSE, /obj/item/stack)
- chosen = SSmaterials.GetMaterialRef(chosen)
-
-
-/obj/machinery/mineral/mint/pickup_item(datum/source, atom/movable/target, atom/oldLoc)
- if(QDELETED(target))
- return
- if(!istype(target, /obj/item/stack))
- return
-
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- var/obj/item/stack/S = target
-
- if(materials.insert_item(S))
- qdel(S)
-
-/obj/machinery/mineral/mint/process()
- if(processing)
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- var/datum/material/M = chosen
-
- if(!M)
- processing = FALSE
- icon_state = "coinpress0"
- return
-
- icon_state = "coinpress1"
- var/coin_mat = MINERAL_MATERIAL_AMOUNT
-
- for(var/sheets in 1 to 2)
- if(materials.use_amount_mat(coin_mat, chosen))
- for(var/coin_to_make in 1 to 5)
- create_coins()
- produced_coins++
- CHECK_TICK
- else
- var/found_new = FALSE
- for(var/datum/material/inserted_material in materials.materials)
- var/amount = materials.get_material_amount(inserted_material)
-
- if(amount)
- chosen = inserted_material
- found_new = TRUE
-
- if(!found_new)
- processing = FALSE
- else
- end_processing()
- icon_state = "coinpress0"
-
-/obj/machinery/mineral/mint/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "Mint", name)
- ui.open()
-
-/obj/machinery/mineral/mint/ui_data()
- var/list/data = list()
- data["inserted_materials"] = list()
- data["chosen_material"] = null
-
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- for(var/datum/material/inserted_material in materials.materials)
- var/amount = materials.get_material_amount(inserted_material)
- if(!amount)
- continue
- data["inserted_materials"] += list(list(
- "material" = inserted_material.name,
- "amount" = amount,
- ))
- if(chosen == inserted_material)
- data["chosen_material"] = inserted_material.name
-
- data["produced_coins"] = produced_coins
- data["processing"] = processing
-
- return data;
-
-/obj/machinery/mineral/mint/ui_act(action, params, datum/tgui/ui)
- . = ..()
- if(.)
- return
- if(action == "startpress")
- if (!processing)
- if(produced_coins > 0)
- log_econ("[produced_coins] coins were created by [src] in the last cycle.")
- produced_coins = 0
- processing = TRUE
- begin_processing()
- return TRUE
- if (action == "stoppress")
- processing = FALSE
- end_processing()
- return TRUE
- if (action == "changematerial")
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- for(var/datum/material/mat in materials.materials)
- if (params["material_name"] == mat.name)
- chosen = mat
- return TRUE
-
-/obj/machinery/mineral/mint/proc/create_coins()
- var/turf/T = get_step(src,output_dir)
- var/temp_list = list()
- temp_list[chosen] = 400
- if(T)
- var/obj/item/O = new /obj/item/coin(src)
- O.set_custom_materials(temp_list)
- if(QDELETED(bag_to_use) || (bag_to_use.loc != T) || !SEND_SIGNAL(bag_to_use, COMSIG_TRY_STORAGE_INSERT, O, null, TRUE)) //important to send the signal so we don't overfill the bag.
- bag_to_use = new(src) //make a new bag if we can't find or use the old one.
- unload_mineral(bag_to_use) //just forcemove memes.
- O.forceMove(bag_to_use) //don't bother sending the signal, the new bag is empty and all that.
-
- SSblackbox.record_feedback("amount", "coins_minted", 1)
diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm
index d92084c9d02..9b49de49120 100644
--- a/code/modules/mining/ores_coins.dm
+++ b/code/modules/mining/ores_coins.dm
@@ -482,4 +482,28 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
/obj/item/coin/iron
+/obj/item/coin/gold/debug
+ custom_materials = list(/datum/material/gold = 400)
+ desc = "If you got this somehow, be aware that it will dust you. Almost certainly."
+
+/obj/item/coin/gold/debug/attack_self(mob/user)
+ if(cooldown < world.time)
+ if(string_attached) //does the coin have a wire attached
+ to_chat(user, "The coin won't flip very well with something attached!" )
+ return FALSE//do not flip the coin
+ cooldown = world.time + 15
+ flick("coin_[coinflip]_flip", src)
+ coinflip = pick(sideslist)
+ icon_state = "coin_[coinflip]"
+ playsound(user.loc, 'sound/items/coinflip.ogg', 50, TRUE)
+ var/oldloc = loc
+ sleep(15)
+ if(loc == oldloc && user && !user.incapacitated())
+ user.visible_message("[user] flips [src]. It lands on [coinflip].", \
+ "You flip [src]. It lands on [coinflip].", \
+ "You hear the clattering of loose change.")
+ SSeconomy.fire()
+ to_chat(user,"[SSeconomy.inflation_value()] is the inflation value.")
+ return TRUE//did the coin flip? useful for suicide_act
+
#undef ORESTACK_OVERLAYS_MAX
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 0f61a9a8f54..656137e8949 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -887,6 +887,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/datum/mafia_controller/game = GLOB.mafia_game //this needs to change if you want multiple mafia games up at once.
if(!game)
game = create_mafia_game("mafia")
+ var/total_slots = game.custom_setup.len ? assoc_value_sum(game.custom_setup) : 12
if(GLOB.mafia_signup[client.ckey])
GLOB.mafia_signup -= ckey
to_chat(usr, "You unregister from Mafia.")
@@ -894,9 +895,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
else
GLOB.mafia_signup[ckey] = client
to_chat(usr, "You sign up for Mafia.")
- to_chat(usr, "The game currently has [GLOB.mafia_signup.len]/12 players signed up.")
+ to_chat(usr, "The game currently has [GLOB.mafia_signup.len]/[total_slots] players signed up.")
if(game.phase != MAFIA_PHASE_SETUP)
- to_chat(usr, "Mafia is currently in progress, you will be signed up for next round.")
+ to_chat(usr, "Mafia is currently in progress, you will be signed up for next round and get messages from the current one.")
else
game.try_autostart()
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index 84c5661a325..96b531ec15b 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -493,7 +493,7 @@
hand_bodyparts[i] = BP
..() //Don't redraw hands until we have organs for them
-//GetAllContenst that is reasonable and not stupid
+//GetAllContents that is reasonable and not stupid
/mob/living/carbon/proc/get_all_gear()
var/list/processing_list = get_equipped_items(include_pockets = TRUE) + held_items
listclearnulls(processing_list) // handles empty hands
diff --git a/code/modules/mob/living/bloodcrawl.dm b/code/modules/mob/living/bloodcrawl.dm
index 75ee9c53c25..ae9ad1a560b 100644
--- a/code/modules/mob/living/bloodcrawl.dm
+++ b/code/modules/mob/living/bloodcrawl.dm
@@ -74,7 +74,7 @@
if(victim.stat == CONSCIOUS)
visible_message("[victim] kicks free of the blood pool just before entering it!", null, "You hear splashing and struggling.")
- else if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/consumable/ethanol/demonsblood, needs_metabolizing = TRUE))
+ else if(victim.reagents?.has_reagent(/datum/reagent/consumable/ethanol/demonsblood, needs_metabolizing = TRUE))
visible_message("Something prevents [victim] from entering the pool!", "A strange force is blocking [victim] from entering!", "You hear a splash and a thud.")
else
victim.forceMove(src)
@@ -107,7 +107,7 @@
if(!victim)
return FALSE
- if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/consumable/ethanol/devilskiss, needs_metabolizing = TRUE))
+ if(victim.reagents?.has_reagent(/datum/reagent/consumable/ethanol/devilskiss, needs_metabolizing = TRUE))
to_chat(src, "AAH! THEIR FLESH! IT BURNS!")
adjustBruteLoss(25) //I can't use adjustHealth() here because bloodcrawl affects /mob/living and adjustHealth() only affects simple mobs
var/found_bloodpool = FALSE
diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm
index 77f104f71c3..87c62f00e01 100644
--- a/code/modules/mob/living/brain/brain_item.dm
+++ b/code/modules/mob/living/brain/brain_item.dm
@@ -26,6 +26,9 @@
var/list/datum/brain_trauma/traumas = list()
+ /// List of skillchip items, their location should be this brain.
+ var/list/obj/item/skillchip/skillchips
+
/obj/item/organ/brain/Insert(mob/living/carbon/C, special = 0,no_id_transfer = FALSE)
..()
@@ -54,10 +57,15 @@
BT.owner = owner
BT.on_gain()
+ for(var/obj/item/skillchip/skill_chip in skillchips)
+ skill_chip.on_apply(owner)
+
//Update the body's icon so it doesnt appear debrained anymore
C.update_hair()
/obj/item/organ/brain/Remove(mob/living/carbon/C, special = 0, no_id_transfer = FALSE)
+ for(var/obj/item/skillchip/skill_chip in skillchips)
+ skill_chip.on_removal(owner)
..()
for(var/X in traumas)
var/datum/brain_trauma/BT = X
@@ -117,6 +125,18 @@
O.reagents.clear_reagents()
return
+ // Cutting out skill chips.
+ if(length(skillchips) && O.sharpness == SHARP_EDGED)
+ to_chat(user,"You begin to excise skillchips from [src].")
+ if(do_after(user, 15 SECONDS, target = src))
+ for(var/obj/item/skillchip/skill_chip in skillchips)
+ if(skill_chip.removable)
+ skill_chip.forceMove(drop_location())
+ else
+ qdel(skill_chip)
+ skillchips = null
+ return
+
if(brainmob) //if we aren't trying to heal the brain, pass the attack onto the brainmob.
O.attack(brainmob, user) //Oh noooeeeee
@@ -127,6 +147,8 @@
/obj/item/organ/brain/examine(mob/user)
. = ..()
+ if(length(skillchips))
+ . += "It has a skillchip embedded in it."
if(suicided)
. += "It's started turning slightly grey. They must not have been able to handle the stress of it all."
return
@@ -181,6 +203,7 @@
if(brainmob)
QDEL_NULL(brainmob)
QDEL_LIST(traumas)
+ QDEL_LIST(skillchips)
return ..()
/obj/item/organ/brain/on_life()
@@ -221,6 +244,19 @@
else
return brain_message
+/obj/item/organ/brain/before_organ_replacement(obj/item/organ/replacement)
+ . = ..()
+ var/obj/item/organ/brain/replacement_brain = replacement
+ if(!istype(replacement_brain))
+ return
+ for(var/obj/item/skillchip/skill_chip in src)
+ if(skill_chip in skillchips)
+ if(owner)
+ skill_chip.on_removal(owner)
+ LAZYREMOVE(skillchips, skill_chip)
+ LAZYADD(replacement_brain.skillchips, skill_chip) //No need to call on_apply here, since it will be inserted in organ replacement soon.
+ skill_chip.forceMove(replacement)
+
/obj/item/organ/brain/alien
name = "alien brain"
desc = "We barely understand the brains of terrestial animals. Who knows what we may find in the brain of such an advanced species?"
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 6b7c183c7af..83c370df242 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -65,17 +65,8 @@
if(!all_wounds || !(user.a_intent == INTENT_HELP || user == src))
return ..()
- // The following priority/nonpriority searching is so that if we have two wounds on a limb that use the same item for treatment (gauze can bandage cuts AND splint broken bones),
- // we prefer whichever wound is not already treated (ignore the splinted broken bone for the open cut). If there's no priority wounds that this can treat, go through the
- // non-priority ones randomly.
- var/list/nonpriority_wounds = list()
- for(var/datum/wound/W in shuffle(all_wounds))
- if(!W.treat_priority)
- nonpriority_wounds += W
- else if(W.treat_priority && W.try_treating(I, user))
- return 1
-
- for(var/datum/wound/W in shuffle(nonpriority_wounds))
+ for(var/i in shuffle(all_wounds))
+ var/datum/wound/W = i
if(W.try_treating(I, user))
return 1
@@ -506,7 +497,7 @@
if(T)
T.add_vomit_floor(src, VOMIT_TOXIC, purge)//toxic barf looks different || call purge when doing detoxicfication to pump more chems out of the stomach.
T = get_step(T, dir)
- if (is_blocked_turf(T))
+ if (T != null && is_blocked_turf(T))
break
return TRUE
@@ -1115,35 +1106,50 @@
if(mood.sanity < SANITY_UNSTABLE)
return TRUE
-/mob/living/carbon/washed(var/atom/washer)
+/mob/living/carbon/wash(clean_types)
. = ..()
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "shower", /datum/mood_event/nice_shower)
- for(var/obj/item/I in held_items)
- I.washed(washer)
+ // Wash equipped stuff that cannot be covered
+ for(var/i in held_items)
+ var/obj/item/held_thing = i
+ if(held_thing.wash(clean_types))
+ . = TRUE
- if(back && back.washed(washer))
+ if(back?.wash(clean_types))
update_inv_back(0)
+ . = TRUE
+ if(head?.wash(clean_types))
+ update_inv_head()
+ . = TRUE
+
+ // Check and wash stuff that can be covered
var/list/obscured = check_obscured_slots()
- if(head && head.washed(washer))
- update_inv_head()
-
- if(glasses && !(ITEM_SLOT_EYES in obscured) && glasses.washed(washer))
+ // If the eyes are covered by anything but glasses, that thing will be covering any potential glasses as well.
+ if(glasses && is_eyes_covered(FALSE, TRUE, TRUE) && glasses.wash(clean_types))
update_inv_glasses()
+ . = TRUE
- if(wear_mask && !(ITEM_SLOT_MASK in obscured && wear_mask.washed(washer)))
+ if(wear_mask && !(ITEM_SLOT_MASK in obscured) && wear_mask.wash(clean_types))
update_inv_wear_mask()
+ . = TRUE
- if(ears && !(HIDEEARS in obscured) && ears.washed(washer))
+ if(ears && !(ITEM_SLOT_EARS in obscured) && ears.wash(clean_types))
update_inv_ears()
+ . = TRUE
- if(wear_neck && !(ITEM_SLOT_NECK in obscured) && wear_neck.washed(washer))
+ if(wear_neck && !(ITEM_SLOT_NECK in obscured) && wear_neck.wash(clean_types))
update_inv_neck()
+ . = TRUE
- if(shoes && !(HIDESHOES in obscured) && shoes.washed(washer))
+ if(shoes && !(ITEM_SLOT_FEET in obscured) && shoes.wash(clean_types))
update_inv_shoes()
+ . = TRUE
+
+ if(gloves && !(ITEM_SLOT_GLOVES in obscured) && gloves.wash(clean_types))
+ update_inv_gloves()
+ . = TRUE
/// if any of our bodyparts are bleeding
/mob/living/carbon/proc/is_bleeding()
@@ -1164,16 +1170,16 @@
/**
* generate_fake_scars()- for when you want to scar someone, but you don't want to hurt them first. These scars don't count for temporal scarring (hence, fake)
*
- * If you want a specific wound scar, pass that wound type as the second arg, otherwise you can pass a list like WOUND_LIST_CUT to generate a random cut scar.
+ * If you want a specific wound scar, pass that wound type as the second arg, otherwise you can pass a list like WOUND_LIST_SLASH to generate a random cut scar.
*
* Arguments:
* * num_scars- A number for how many scars you want to add
- * * forced_type- Which wound or category of wounds you want to choose from, WOUND_LIST_BONE, WOUND_LIST_CUT, or WOUND_LIST_BURN (or some combination). If passed a list, picks randomly from the listed wounds. Defaults to all 3 types
+ * * forced_type- Which wound or category of wounds you want to choose from, WOUND_LIST_BLUNT, WOUND_LIST_SLASH, or WOUND_LIST_BURN (or some combination). If passed a list, picks randomly from the listed wounds. Defaults to all 3 types
*/
/mob/living/carbon/proc/generate_fake_scars(num_scars, forced_type)
for(var/i in 1 to num_scars)
- var/datum/scar/S = new
- var/obj/item/bodypart/BP = pick(bodyparts)
+ var/datum/scar/scaries = new
+ var/obj/item/bodypart/scar_part = pick(bodyparts)
var/wound_type
if(forced_type)
@@ -1182,12 +1188,54 @@
else
wound_type = forced_type
else
- wound_type = pick(WOUND_LIST_BONE + WOUND_LIST_CUT + WOUND_LIST_BURN)
+ wound_type = pick(GLOB.global_all_wound_types)
- var/datum/wound/W = new wound_type
- S.generate(BP, W)
- S.fake = TRUE
- QDEL_NULL(W)
+ var/datum/wound/phantom_wound = new wound_type
+ scaries.generate(scar_part, phantom_wound)
+ scaries.fake = TRUE
+ QDEL_NULL(phantom_wound)
/mob/living/carbon/is_face_visible()
return !(wear_mask?.flags_inv & HIDEFACE) && !(head?.flags_inv & HIDEFACE)
+
+/**
+ * get_biological_state is a helper used to see what kind of wounds we roll for. By default we just assume carbons (read:monkeys) are flesh and bone, but humans rely on their species datums
+ *
+ * go look at the species def for more info [/datum/species/proc/get_biological_state]
+ */
+/mob/living/carbon/proc/get_biological_state()
+ return BIO_FLESH_BONE
+
+/// Modifies max_skillchip_count and updates active skillchips
+/mob/living/carbon/proc/adjust_max_skillchip_count(delta)
+ max_skillchip_slots += delta
+ update_skillchips()
+
+/// Disables or re-enables any extra skillchips after skillchip limit changes. Inactive chips keep brain as loc but do not appear in skillchips list.
+/mob/living/carbon/proc/update_skillchips()
+ var/obj/item/organ/brain/B = getorganslot(ORGAN_SLOT_BRAIN)
+ if(!B)
+ return
+ var/limit = max_skillchip_slots
+ var/dt = limit - used_skillchip_slots
+ var/list/inactive_skillchips = list()
+ for(var/obj/item/skillchip/S in B)
+ inactive_skillchips += S
+
+ // We have skillchips to deactivate
+ if(dt < 0)
+ //Might deactivate more than necessary but not worth sorting this
+ while(dt < 0)
+ var/obj/item/skillchip/chip = B.skillchips[length(B.skillchips)]
+ chip.on_removal(src)
+ B.skillchips -= chip
+ dt += chip.slot_cost
+ // We have skillchips to reactivate
+ else if (dt > 1)
+ while(dt > 1 && length(inactive_skillchips))
+ var/obj/item/skillchip/chip = inactive_skillchips[length(inactive_skillchips)]
+ if(chip.slot_cost <= dt)
+ chip.on_apply(src)
+ B.skillchips += chip
+ dt -= chip.slot_cost
+ inactive_skillchips -= chip
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 6d332704428..473415f8de8 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -75,7 +75,7 @@
if(!affecting) //missing limb? we select the first bodypart (you can never have zero, because of chest)
affecting = bodyparts[1]
SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting)
- send_item_attack_message(I, user, affecting.name)
+ send_item_attack_message(I, user, affecting.name, affecting)
if(I.force)
apply_damage(I.force, I.damtype, affecting, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness())
if(I.damtype == BRUTE && affecting.status == BODYPART_ORGANIC)
@@ -96,14 +96,41 @@
head.add_mob_blood(src)
update_inv_head()
- //dismemberment
- var/probability = I.get_dismemberment_chance(affecting)
- if(prob(probability))
- if(affecting.dismember(I.damtype))
- I.add_mob_blood(src)
- playsound(get_turf(src), I.get_dismember_sound(), 80, TRUE)
return TRUE //successful attack
+/mob/living/carbon/send_item_attack_message(obj/item/I, mob/living/user, hit_area, obj/item/bodypart/hit_bodypart)
+ var/message_verb = "attacked"
+ if(length(I.attack_verb))
+ message_verb = "[pick(I.attack_verb)]"
+ else if(!I.force)
+ return
+
+ var/extra_wound_details = ""
+ if(I.damtype == BRUTE && hit_bodypart.can_dismember())
+ var/mangled_state = hit_bodypart.get_mangled_state()
+ var/bio_state = get_biological_state()
+ if(mangled_state == BODYPART_MANGLED_BOTH)
+ extra_wound_details = ", threatening to sever it entirely"
+ else if((mangled_state == BODYPART_MANGLED_FLESH && I.get_sharpness()) || (mangled_state & BODYPART_MANGLED_BONE && bio_state == BIO_JUST_BONE))
+ extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the bone"
+ else if((mangled_state == BODYPART_MANGLED_BONE && I.get_sharpness()) || (mangled_state & BODYPART_MANGLED_FLESH && bio_state == BIO_JUST_FLESH))
+ extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining tissue"
+
+ var/message_hit_area = ""
+ if(hit_area)
+ message_hit_area = " in the [hit_area]"
+ var/attack_message = "[src] is [message_verb][message_hit_area] with [I][extra_wound_details]!"
+ var/attack_message_local = "You're [message_verb][message_hit_area] with [I][extra_wound_details]!"
+ if(user in viewers(src, null))
+ attack_message = "[user] [message_verb] [src][message_hit_area] with [I][extra_wound_details]!"
+ attack_message_local = "[user] [message_verb] you[message_hit_area] with [I][extra_wound_details]!"
+ if(user == src)
+ attack_message_local = "You [message_verb] yourself[message_hit_area] with [I][extra_wound_details]"
+ visible_message("[attack_message]",\
+ "[attack_message_local]", null, COMBAT_MESSAGE_RANGE)
+ return TRUE
+
+
/mob/living/carbon/attack_drone(mob/living/simple_animal/drone/user)
return //so we don't call the carbon's attack_hand().
@@ -124,13 +151,14 @@
if(!(mobility_flags & MOBILITY_STAND) || !S.lying_required)
if(user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM)
if(S.next_step(user, user.a_intent))
- return 1
+ return TRUE
- for(var/datum/wound/W in all_wounds)
+ for(var/i in all_wounds)
+ var/datum/wound/W = i
if(W.try_handling(user))
- return 1
+ return TRUE
- return 0
+ return FALSE
/mob/living/carbon/attack_paw(mob/living/carbon/monkey/M)
@@ -148,13 +176,13 @@
if(M.a_intent == INTENT_HELP)
help_shake_act(M)
- return 0
+ return FALSE
if(..()) //successful monkey bite.
for(var/thing in M.diseases)
var/datum/disease/D = thing
ForceContractDisease(D)
- return 1
+ return TRUE
/mob/living/carbon/attack_slime(mob/living/simple_animal/slime/M)
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index 636d13c2dc7..489425213f8 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -77,3 +77,8 @@
var/list/all_wounds
/// All of the scars a carbon has afflicted throughout their limbs
var/list/all_scars
+
+ /// Maximum number of skillchips slots we can support before they stop working
+ var/max_skillchip_slots = 2
+ /// Currently used skillchip slots
+ var/used_skillchip_slots = 0
diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm
index c64dd449cfc..df5067da7d0 100644
--- a/code/modules/mob/living/carbon/damage_procs.dm
+++ b/code/modules/mob/living/carbon/damage_procs.dm
@@ -1,6 +1,6 @@
-/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMGE, damage, damagetype, def_zone)
var/hit_percent = (100-blocked)/100
if(!damage || (!forced && hit_percent <= 0))
@@ -209,7 +209,7 @@
*
* It automatically updates health status
*/
-/mob/living/carbon/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status, check_armor = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/carbon/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status, check_armor = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
var/list/obj/item/bodypart/parts = get_damageable_bodyparts(required_status)
if(!parts.len)
return
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index 0040bed767f..12ecaffcd3f 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -45,7 +45,8 @@
msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!\n"
else
msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n"
- for(var/datum/wound/W in BP.wounds)
+ for(var/i in BP.wounds)
+ var/datum/wound/W = i
msg += "[W.get_examine_description(user)]\n"
for(var/X in disabled)
@@ -111,7 +112,7 @@
switch(scar_severity)
if(1 to 2)
- msg += "[t_He] [t_has] visible scarring, you can look again to take a closer look...\n"
+ msg += "[t_He] [t_has] visible scarring, you can look again to take a closer look...\n"
if(3 to 4)
msg += "[t_He] [t_has] several bad scars, you can look again to take a closer look...\n"
if(5 to 6)
diff --git a/code/modules/mob/living/carbon/human/damage_procs.dm b/code/modules/mob/living/carbon/human/damage_procs.dm
index 231f85097b0..0947cfe49db 100644
--- a/code/modules/mob/living/carbon/human/damage_procs.dm
+++ b/code/modules/mob/living/carbon/human/damage_procs.dm
@@ -1,4 +1,4 @@
/// depending on the species, it will run the corresponding apply_damage code there
-/mob/living/carbon/human/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/carbon/human/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, forced, spread_damage, wound_bonus, bare_wound_bonus, sharpness)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index b6e484e4a32..1a17d1d0603 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -151,8 +151,9 @@
else
msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n"
- for(var/datum/wound/W in BP.wounds)
- msg += "[W.get_examine_description(user)]\n"
+ for(var/i in BP.wounds)
+ var/datum/wound/iter_wound = i
+ msg += "[iter_wound.get_examine_description(user)]\n"
for(var/X in disabled)
var/obj/item/bodypart/BP = X
@@ -259,7 +260,13 @@
bleeding_limbs += BP
var/num_bleeds = LAZYLEN(bleeding_limbs)
- var/bleed_text = "[t_He] [t_is] bleeding from [t_his]"
+
+ var/list/bleed_text
+ if(appears_dead)
+ bleed_text = list("Blood is visible in [t_his] open")
+ else
+ bleed_text = list("[t_He] [t_is] bleeding from [t_his]")
+
switch(num_bleeds)
if(1 to 2)
bleed_text += " [bleeding_limbs[1].name][num_bleeds == 2 ? " and [bleeding_limbs[2].name]" : ""]"
@@ -268,11 +275,15 @@
var/obj/item/bodypart/BP = bleeding_limbs[i]
bleed_text += " [BP.name],"
bleed_text += " and [bleeding_limbs[num_bleeds].name]"
- if(reagents.has_reagent(/datum/reagent/toxin/heparin, needs_metabolizing = TRUE))
- bleed_text += " incredibly quickly"
- bleed_text += "!\n"
- msg += bleed_text
+ if(appears_dead)
+ bleed_text += ", but it has pooled and is not flowing.\n"
+ else
+ if(reagents.has_reagent(/datum/reagent/toxin/heparin, needs_metabolizing = TRUE))
+ bleed_text += " incredibly quickly"
+
+ bleed_text += "!\n"
+ msg += bleed_text.Join()
if(reagents.has_reagent(/datum/reagent/teslium, needs_metabolizing = TRUE))
msg += "[t_He] [t_is] emitting a gentle blue glow!\n"
@@ -349,7 +360,7 @@
switch(scar_severity)
if(1 to 2)
- msg += "[t_He] [t_has] visible scarring, you can look again to take a closer look...\n"
+ msg += "[t_He] [t_has] visible scarring, you can look again to take a closer look...\n"
if(3 to 4)
msg += "[t_He] [t_has] several bad scars, you can look again to take a closer look...\n"
if(5 to 6)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 8b5ce86bc13..33828f2fc6c 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -18,7 +18,7 @@
. = ..()
- RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood)
+ RegisterSignal(src, COMSIG_COMPONENT_CLEAN_FACE_ACT, .proc/clean_face)
AddComponent(/datum/component/personal_crafting)
AddComponent(/datum/component/footstep, FOOTSTEP_MOB_HUMAN, 1, 2)
GLOB.human_list += src
@@ -700,16 +700,82 @@
if(..())
dropItemToGround(I)
-/mob/living/carbon/human/proc/clean_blood(datum/source, strength)
- if(strength < CLEAN_STRENGTH_BLOOD)
- return
+/**
+ * Wash the hands, cleaning either the gloves if equipped and not obscured, otherwise the hands themselves if they're not obscured.
+ *
+ * Returns false if we couldn't wash our hands due to them being obscured, otherwise true
+ */
+/mob/living/carbon/human/proc/wash_hands(clean_types)
+ var/list/obscured = check_obscured_slots()
+ if(ITEM_SLOT_GLOVES in obscured)
+ return FALSE
+
if(gloves)
- if(SEND_SIGNAL(gloves, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD))
- update_inv_gloves()
- else
- if(bloody_hands)
- bloody_hands = 0
+ if(gloves.wash(clean_types))
update_inv_gloves()
+ else if((clean_types & CLEAN_TYPE_BLOOD) && blood_in_hands > 0)
+ blood_in_hands = 0
+ update_inv_gloves()
+
+ return TRUE
+
+/**
+ * Cleans the lips of any lipstick. Returns TRUE if the lips had any lipstick and was thus cleaned
+ */
+/mob/living/carbon/human/proc/clean_lips()
+ if(isnull(lip_style) && lip_color == initial(lip_color))
+ return FALSE
+ lip_style = null
+ lip_color = initial(lip_color)
+ update_body()
+ return TRUE
+
+/**
+ * Called on the COMSIG_COMPONENT_CLEAN_FACE_ACT signal
+ */
+/mob/living/carbon/human/proc/clean_face(datum/source, clean_types)
+ if(!is_mouth_covered() && clean_lips())
+ . = TRUE
+
+ if(glasses && is_eyes_covered(FALSE, TRUE, TRUE) && glasses.wash(clean_types))
+ update_inv_glasses()
+ . = TRUE
+
+ var/list/obscured = check_obscured_slots()
+ if(wear_mask && !(ITEM_SLOT_MASK in obscured) && wear_mask.wash(clean_types))
+ update_inv_wear_mask()
+ . = TRUE
+
+/**
+ * Called when this human should be washed
+ */
+/mob/living/carbon/human/wash(clean_types)
+ . = ..()
+
+ // Wash equipped stuff that cannot be covered
+ if(wear_suit?.wash(clean_types))
+ update_inv_wear_suit()
+ . = TRUE
+
+ if(belt?.wash(clean_types))
+ update_inv_belt()
+ . = TRUE
+
+ // Check and wash stuff that can be covered
+ var/list/obscured = check_obscured_slots()
+
+ if(w_uniform && !(ITEM_SLOT_ICLOTHING in obscured) && w_uniform.wash(clean_types))
+ update_inv_w_uniform()
+ . = TRUE
+
+ if(!is_mouth_covered() && clean_lips())
+ . = TRUE
+
+ // Wash hands if exposed
+ if(!gloves && (clean_types & CLEAN_TYPE_BLOOD) && blood_in_hands > 0 && !(ITEM_SLOT_GLOVES in obscured))
+ blood_in_hands = 0
+ update_inv_gloves()
+ . = TRUE
//Turns a mob black, flashes a skeleton overlay
//Just like a cartoon!
@@ -1103,24 +1169,6 @@
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown)
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying)
-/mob/living/carbon/human/washed(var/atom/washer)
- . = ..()
- if(wear_suit && wear_suit.washed(washer))
- update_inv_wear_suit()
- else if(w_uniform && w_uniform.washed(washer))
- update_inv_w_uniform()
-
- if(!is_mouth_covered())
- lip_style = null
- update_body()
- if(belt && belt.washed(washer))
- update_inv_belt()
-
- var/list/obscured = check_obscured_slots()
-
- if(gloves && !(HIDEGLOVES in obscured) && gloves.washed(washer))
- update_inv_gloves()
-
/mob/living/carbon/human/adjust_nutrition(change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks
if(HAS_TRAIT(src, TRAIT_NOHUNGER))
return FALSE
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index ac5e7db5041..144c6d936b6 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -64,3 +64,7 @@
var/hardcore_survival_score = 0
/// For agendered spessmen, which body type to use
var/body_type = MALE
+
+ /// How many "units of blood" we have on our hands
+ var/blood_in_hands = 0
+
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index cb13e795c63..6bf01049be9 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -178,19 +178,29 @@
/// For use formatting all of the scars this human has for saving for persistent scarring
/mob/living/carbon/human/proc/format_scars()
- if(!all_scars)
+ var/list/missing_bodyparts = get_missing_limbs()
+ if(!all_scars && !length(missing_bodyparts))
return
var/scars = ""
+ for(var/i in missing_bodyparts)
+ var/datum/scar/scaries = new
+ scars += "[scaries.format_amputated(i)]"
for(var/i in all_scars)
- var/datum/scar/S = i
- scars += "[S.format()];"
+ var/datum/scar/scaries = i
+ scars += "[scaries.format()];"
return scars
/// Takes a single scar from the persistent scar loader and recreates it from the saved data
/mob/living/carbon/human/proc/load_scar(scar_line)
var/list/scar_data = splittext(scar_line, "|")
- if(LAZYLEN(scar_data) != 4)
+ if(LAZYLEN(scar_data) != SCAR_SAVE_LENGTH)
return // invalid, should delete
- var/obj/item/bodypart/BP = get_bodypart("[scar_data[SCAR_SAVE_ZONE]]")
- var/datum/scar/S = new
- return S.load(BP, scar_data[SCAR_SAVE_DESC], scar_data[SCAR_SAVE_PRECISE_LOCATION], text2num(scar_data[SCAR_SAVE_SEVERITY]))
+ var/version = text2num(scar_data[SCAR_SAVE_VERS])
+ if(!version || version < SCAR_CURRENT_VERSION) // get rid of old scars
+ return
+ var/obj/item/bodypart/the_part = get_bodypart("[scar_data[SCAR_SAVE_ZONE]]")
+ var/datum/scar/scaries = new
+ return scaries.load(the_part, scar_data[SCAR_SAVE_VERS], scar_data[SCAR_SAVE_DESC], scar_data[SCAR_SAVE_PRECISE_LOCATION], text2num(scar_data[SCAR_SAVE_SEVERITY]))
+
+/mob/living/carbon/human/get_biological_state()
+ return dna.species.get_biological_state()
diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm
index 16320113a8b..2ff1204844a 100644
--- a/code/modules/mob/living/carbon/human/human_say.dm
+++ b/code/modules/mob/living/carbon/human/human_say.dm
@@ -80,27 +80,4 @@
/mob/living/carbon/human/get_alt_name()
if(name != GetVoice())
- return " (as [get_id_name("Unknown")])"
-
-/mob/living/carbon/human/proc/forcesay(list/append) //this proc is at the bottom of the file because quote fuckery makes notepad++ cri
- if(stat == CONSCIOUS)
- if(client)
- var/temp = winget(client, "input", "text")
- var/say_starter = "Say \"" //"
- if(findtextEx(temp, say_starter, 1, length(say_starter) + 1) && length(temp) > length(say_starter)) //case sensitive means
-
- temp = trim_left(copytext(temp, length(say_starter) + 1))
- temp = replacetext(temp, ";", "", 1, 2) //general radio
- while(trim_left(temp)[1] == ":") //dept radio again (necessary)
- temp = copytext_char(trim_left(temp), 3)
-
- if(temp[1] == "*") //emotes
- return
-
- var/trimmed = trim_left(temp)
- if(length(trimmed))
- if(append)
- trimmed += pick(append)
-
- say(trimmed)
- winset(client, "input", "text=[null]")
+ return " (as [get_id_name("Unknown")])"\
diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm
index 92bd6b3ec4b..fe32c33cf0a 100644
--- a/code/modules/mob/living/carbon/human/human_update_icons.dm
+++ b/code/modules/mob/living/carbon/human/human_update_icons.dm
@@ -173,7 +173,7 @@ There are several things that need to be remembered:
var/obj/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_GLOVES) + 1]
inv.update_icon()
- if(!gloves && bloody_hands)
+ if(!gloves && blood_in_hands)
var/mutable_appearance/bloody_overlay = mutable_appearance('icons/effects/blood.dmi', "bloodyhands", -GLOVES_LAYER)
if(get_num_arms(FALSE) < 2)
if(has_left_hand(FALSE))
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 454eaea180f..70bf9cce8fa 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -40,6 +40,10 @@
handle_liver()
dna.species.spec_life(src) // for mutantraces
+ else
+ for(var/i in all_wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.on_stasis()
//Update our name based on whether our face is obscured/disfigured
name = get_visible_name()
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 946c3fce6f2..66f3c4536a3 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -283,10 +283,12 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(slot == ORGAN_SLOT_BRAIN)
var/obj/item/organ/brain/brain = oldorgan
if(!brain.decoy_override)//"Just keep it if it's fake" - confucius, probably
+ brain.before_organ_replacement(neworgan)
brain.Remove(C,TRUE, TRUE) //brain argument used so it doesn't cause any... sudden death.
QDEL_NULL(brain)
oldorgan = null //now deleted
else
+ oldorgan.before_organ_replacement(neworgan)
oldorgan.Remove(C,TRUE)
QDEL_NULL(oldorgan) //we cannot just tab this out because we need to skip the deleting if it is a decoy brain.
@@ -1383,10 +1385,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
to_chat(user, "You knock [target] down!")
var/knockdown_duration = 40 + (target.getStaminaLoss() + (target.getBruteLoss()*0.5))*0.8 //50 total damage = 40 base stun + 40 stun modifier = 80 stun duration, which is the old base duration
target.apply_effect(knockdown_duration, EFFECT_KNOCKDOWN, armor_block)
- target.forcesay(GLOB.hit_appends)
log_combat(user, target, "got a stun punch with their previous punch")
- else if(!(target.mobility_flags & MOBILITY_STAND))
- target.forcesay(GLOB.hit_appends)
/datum/species/proc/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target)
return
@@ -1559,7 +1558,6 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/armor_block = H.run_armor_check(affecting, "melee", "Your armor has protected your [hit_area]!", "Your armor has softened a hit to your [hit_area]!",I.armour_penetration)
armor_block = min(90,armor_block) //cap damage reduction at 90%
- var/Iforce = I.force //to avoid runtimes on the forcesay checks at the bottom. Some items might delete themselves if you drop them. (stunning yourself, ninja swords)
var/Iwound_bonus = I.wound_bonus
// this way, you can't wound with a surgical tool on help intent if they have a surgery active and are laying down, so a misclick with a circular saw on the wrong limb doesn't bleed them dry (they still get hit tho)
@@ -1569,18 +1567,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/weakness = H.check_weakness(I, user)
apply_damage(I.force * weakness, I.damtype, def_zone, armor_block, H, wound_bonus = Iwound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness())
- H.send_item_attack_message(I, user, hit_area)
+ H.send_item_attack_message(I, user, hit_area, affecting)
if(!I.force)
return 0 //item force is zero
- //dismemberment
- var/probability = I.get_dismemberment_chance(affecting)
- if(prob(probability) || (HAS_TRAIT(H, TRAIT_EASYDISMEMBER) && prob(probability))) //try twice
- if(affecting.dismember(I.damtype))
- I.add_mob_blood(H)
- playsound(get_turf(H), I.get_dismember_sound(), 80, TRUE)
-
var/bloody = 0
if(((I.damtype == BRUTE) && I.force && prob(25 + (I.force * 2))))
if(affecting.status == BODYPART_ORGANIC)
@@ -1639,11 +1630,9 @@ GLOBAL_LIST_EMPTY(roundstart_races)
H.w_uniform.add_mob_blood(H)
H.update_inv_w_uniform()
- if(Iforce > 10 || Iforce >= 5 && prob(33))
- H.forcesay(GLOB.hit_appends) //forcesay checks stat already.
return TRUE
-/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
SEND_SIGNAL(H, COMSIG_MOB_APPLY_DAMGE, damage, damagetype, def_zone, wound_bonus, bare_wound_bonus, sharpness) // make sure putting wound_bonus here doesn't screw up other signals or uses for this signal
var/hit_percent = (100-(blocked+armor))/100
hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100
@@ -2117,3 +2106,13 @@ GLOBAL_LIST_EMPTY(roundstart_races)
else
to_chat(H, "You beat your wings and begin to hover gently above the ground...")
H.set_resting(FALSE, TRUE)
+
+/**
+ * The human species version of [/mob/living/carbon/proc/get_biological_state]. Depends on the HAS_FLESH and HAS_BONE species traits, having bones lets you have bone wounds, having flesh lets you have burn, slash, and piercing wounds
+ */
+/datum/species/proc/get_biological_state(mob/living/carbon/human/H)
+ . = BIO_INORGANIC
+ if(HAS_FLESH in species_traits)
+ . |= BIO_JUST_FLESH
+ if(HAS_BONE in species_traits)
+ . |= BIO_JUST_BONE
diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
index 9935717ba80..f977dd0c4aa 100644
--- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
@@ -2,7 +2,7 @@
name = "Dullahan"
id = "dullahan"
default_color = "FFFFFF"
- species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS)
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS, HAS_FLESH, HAS_BONE)
inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH)
default_features = list("mcolor" = "FFF", "tail_human" = "None", "ears" = "None", "wings" = "None")
use_skintones = TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
index 7153cacfdcb..3db33a17cb6 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -15,7 +15,7 @@
payday_modifier = 0.75
attack_type = BURN //burn bish
damage_overlay_type = "" //We are too cool for regular damage overlays
- species_traits = list(DYNCOLORS, AGENDER, NO_UNDERWEAR, HAIR)
+ species_traits = list(DYNCOLORS, AGENDER, NO_UNDERWEAR, HAIR, HAS_FLESH, HAS_BONE) // i mean i guess they have blood so they can have wounds too
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
species_language_holder = /datum/language_holder/ethereal
inherent_traits = list(TRAIT_NOHUNGER)
diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index 3cb9bc4237d..8c59ab07567 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -914,7 +914,7 @@
special_names = list("Head", "Broth", "Fracture", "Rattler", "Appetit")
liked_food = GROSS | MEAT | RAW
toxic_food = null
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,HAS_BONE)
inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID
mutanttongue = /obj/item/organ/tongue/bone
sexes = FALSE
@@ -942,8 +942,10 @@
H.reagents.remove_reagent(chem.type, chem.volume - 10)
to_chat(H, "The excess milk is dripping off your bones!")
H.heal_bodypart_damage(1.5,0, 0)
+ for(var/i in H.all_wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.on_xadone(2)
H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
- return TRUE
if(chem.type == /datum/reagent/toxin/bonehurtingjuice)
H.adjustStaminaLoss(7.5, 0)
H.adjustBruteLoss(0.5, 0)
diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm
index 2c61d6a5e61..2c80700c589 100644
--- a/code/modules/mob/living/carbon/human/species_types/humans.dm
+++ b/code/modules/mob/living/carbon/human/species_types/humans.dm
@@ -2,7 +2,7 @@
name = "Human"
id = "human"
default_color = "FFFFFF"
- species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,CAN_SCAR)
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,HAS_FLESH,HAS_BONE)
default_features = list("mcolor" = "FFF", "wings" = "None")
use_skintones = 1
skinned_type = /obj/item/stack/sheet/animalhide/human
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 271245d0e26..42a054bc6b9 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -641,6 +641,8 @@
/datum/action/innate/linked_speech/Activate()
var/mob/living/carbon/human/H = owner
+ if(H.stat == DEAD)
+ return
if(!species || !(H in species.linked_mobs))
to_chat(H, "The link seems to have been severed...")
Remove(H)
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index 3e8654bf684..ed7f3df50d3 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -4,7 +4,7 @@
id = "lizard"
say_mod = "hisses"
default_color = "00FF00"
- species_traits = list(MUTCOLORS,EYECOLOR,LIPS,CAN_SCAR)
+ species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAS_FLESH,HAS_BONE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_REPTILE
mutant_bodyparts = list("tail_lizard", "snout", "spines", "horns", "frills", "body_markings", "legs")
mutanttongue = /obj/item/organ/tongue/lizard
@@ -84,6 +84,6 @@
name = "Ash Walker"
id = "ashlizard"
limbs_id = "lizard"
- species_traits = list(MUTCOLORS,EYECOLOR,LIPS,DIGITIGRADE,CAN_SCAR)
+ species_traits = list(MUTCOLORS,EYECOLOR,LIPS,DIGITIGRADE,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_CHUNKYFINGERS,TRAIT_NOBREATH)
species_language_holder = /datum/language_holder/lizard/ash
diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
index c28fa0768e0..c6704cef159 100644
--- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
@@ -3,7 +3,7 @@
id = "moth"
say_mod = "flutters"
default_color = "00FF00"
- species_traits = list(LIPS, NOEYESPRITES, CAN_SCAR)
+ species_traits = list(LIPS, NOEYESPRITES, HAS_FLESH, HAS_BONE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG
mutant_bodyparts = list("moth_wings", "moth_markings")
default_features = list("moth_wings" = "Plain", "moth_markings" = "None")
diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
index 8e302abbb29..ca20e5e6aa7 100644
--- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
@@ -10,7 +10,7 @@
nojumpsuit = TRUE
say_mod = "poofs" //what does a mushroom sound like
- species_traits = list(MUTCOLORS, NOEYESPRITES, NO_UNDERWEAR)
+ species_traits = list(MUTCOLORS, NOEYESPRITES, NO_UNDERWEAR, HAS_FLESH, HAS_BONE)
inherent_traits = list(TRAIT_NOBREATH, TRAIT_NOFLASH)
inherent_factions = list("mushroom")
speedmod = 1.5 //faster than golems but not by much
diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
index 6e6a1b7ba4f..51327257ed5 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -4,7 +4,7 @@
say_mod = "rattles"
sexes = 0
meat = /obj/item/stack/sheet/mineral/plasma
- species_traits = list(NOBLOOD,NOTRANSSTING)
+ species_traits = list(NOBLOOD,NOTRANSSTING, HAS_BONE)
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_NOHUNGER,TRAIT_ALWAYS_CLEAN)
inherent_biotypes = MOB_HUMANOID|MOB_MINERAL
mutantlungs = /obj/item/organ/lungs/plasmaman
@@ -150,14 +150,23 @@
/datum/species/plasmaman/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
. = ..()
- if(chem.type == /datum/reagent/consumable/milk)
+ if(istype(chem, /datum/reagent/consumable/milk))
if(chem.volume > 10)
H.reagents.remove_reagent(chem.type, chem.volume - 10)
to_chat(H, "The excess milk is dripping off your bones!")
H.heal_bodypart_damage(1.5,0, 0)
H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ for(var/i in H.all_wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.on_xadone(2)
return TRUE
- if(chem.type == /datum/reagent/toxin/bonehurtingjuice)
+ if(istype(chem, /datum/reagent/toxin/plasma))
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ for(var/i in H.all_wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.on_xadone(4) // plasmamen use plasma to reform their bones or whatever
+ return TRUE
+ if(istype(chem, /datum/reagent/toxin/bonehurtingjuice))
H.adjustStaminaLoss(7.5, 0)
H.adjustBruteLoss(0.5, 0)
if(prob(20))
diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
index 4d9f557f02d..742ca01cd67 100644
--- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
@@ -3,7 +3,7 @@
name = "Podperson"
id = "pod"
default_color = "59CE00"
- species_traits = list(MUTCOLORS,EYECOLOR)
+ species_traits = list(MUTCOLORS,EYECOLOR, HAS_FLESH, HAS_BONE)
inherent_traits = list(TRAIT_ALWAYS_CLEAN)
inherent_factions = list("plants", "vines")
attack_verb = "slash"
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index d2ec289fdcb..b3102eab051 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -172,7 +172,7 @@
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
item_flags = ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
wound_bonus = -60
bare_wound_bonus = 20
@@ -187,37 +187,63 @@
return
if(isopenturf(AM)) //So you can actually melee with it
return
+
if(isliving(AM))
var/mob/living/L = AM
if(isethereal(AM))
AM.emp_act(EMP_LIGHT)
- if(iscyborg(AM))
+ else if(iscyborg(AM))
var/mob/living/silicon/robot/borg = AM
if(borg.lamp_intensity)
borg.update_headlamp(TRUE, INFINITY)
to_chat(borg, "Your headlamp is fried! You'll need a human to help replace it.")
- else
- for(var/obj/item/O in AM)
+ else if(ishuman(AM))
+ var/mob/living/carbon/human/H = AM
+ for(var/obj/item/O in H.get_all_gear()) //less expensive than getallcontents
if(O.light_range && O.light_power)
- disintegrate(O)
+ disintegrate(O, AM)
+ else
+ for(var/obj/item/O in AM.GetAllContents())
+ if(O.light_range && O.light_power)
+ disintegrate(O, AM)
if(L.pulling && L.pulling.light_range && isitem(L.pulling))
- disintegrate(L.pulling)
+ disintegrate(L.pulling, L.pulling)
+
else if(isitem(AM))
var/obj/item/I = AM
if(I.light_range && I.light_power)
- disintegrate(I)
+ disintegrate(I, I)
-/obj/item/light_eater/proc/disintegrate(obj/item/O)
+ else if(ismecha(AM))
+ var/obj/mecha/M = AM
+ if(M.haslights)
+ M.visible_message("[M]'s lights burn out!")
+ M.haslights = FALSE
+ M.set_light(-M.lights_power)
+ if(M.occupant)
+ M.lights_action.Remove(M.occupant)
+ for(var/obj/item/O in AM.GetAllContents())
+ if(O.light_range && O.light_power)
+ disintegrate(O, M)
+
+ else if(istype(AM, /obj/machinery/light))
+ var/obj/machinery/light/L = AM
+ if(L.status == 1)
+ return
+ disintegrate(L.drop_light_tube(), AM)
+
+
+/obj/item/light_eater/proc/disintegrate(obj/item/O, atom/A)
if(istype(O, /obj/item/pda))
var/obj/item/pda/PDA = O
PDA.set_light(0)
PDA.fon = FALSE
PDA.f_lum = 0
PDA.update_icon()
- visible_message("The light in [PDA] shorts out!")
+ A.visible_message("The light in [PDA] shorts out!")
else
- visible_message("[O] is disintegrated by [src]!")
+ A.visible_message("[O] is disintegrated by [src]!")
O.burn()
playsound(src, 'sound/items/welder.ogg', 50, TRUE)
diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
index 3ea96e01468..7443224e7f6 100644
--- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm
+++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
@@ -5,7 +5,7 @@
say_mod = "rattles"
sexes = 0
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/skeleton
- species_traits = list(NOBLOOD)
+ species_traits = list(NOBLOOD, HAS_BONE)
inherent_traits = list(TRAIT_NOMETABOLISM,TRAIT_TOXIMMUNE,TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_GENELESS,\
TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH,TRAIT_XENO_IMMUNE,TRAIT_NOCLONELOSS)
inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID
@@ -29,6 +29,9 @@
if(chem.volume > 10)
H.reagents.remove_reagent(chem.type, chem.volume - 10)
to_chat(H, "The excess milk is dripping off your bones!")
+ for(var/i in H.all_wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.on_xadone(2)
H.heal_bodypart_damage(1,1, 0)
H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
return TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/snail.dm b/code/modules/mob/living/carbon/human/species_types/snail.dm
index 73b133f46f3..0b4c9537089 100644
--- a/code/modules/mob/living/carbon/human/species_types/snail.dm
+++ b/code/modules/mob/living/carbon/human/species_types/snail.dm
@@ -3,7 +3,7 @@
id = "snail"
offset_features = list(OFFSET_UNIFORM = list(0,0), OFFSET_ID = list(0,0), OFFSET_GLOVES = list(0,0), OFFSET_GLASSES = list(0,4), OFFSET_EARS = list(0,0), OFFSET_SHOES = list(0,0), OFFSET_S_STORE = list(0,0), OFFSET_FACEMASK = list(0,0), OFFSET_HEAD = list(0,0), OFFSET_FACE = list(0,0), OFFSET_BELT = list(0,0), OFFSET_BACK = list(0,0), OFFSET_SUIT = list(0,0), OFFSET_NECK = list(0,0))
default_color = "336600" //vomit green
- species_traits = list(MUTCOLORS, NO_UNDERWEAR)
+ species_traits = list(MUTCOLORS, NO_UNDERWEAR, HAS_FLESH, HAS_BONE)
inherent_traits = list(TRAIT_ALWAYS_CLEAN, TRAIT_NOSLIPALL)
attack_verb = "slap"
say_mod = "slurs"
diff --git a/code/modules/mob/living/carbon/human/species_types/synths.dm b/code/modules/mob/living/carbon/human/species_types/synths.dm
index 266a3e09142..86effae9c1b 100644
--- a/code/modules/mob/living/carbon/human/species_types/synths.dm
+++ b/code/modules/mob/living/carbon/human/species_types/synths.dm
@@ -9,7 +9,7 @@
meat = null
damage_overlay_type = "synth"
limbs_id = "synth"
- var/disguise_fail_health = 75 //When their health gets to this level their instabitaluri partially falls off
+ var/disguise_fail_health = 75 //When their health gets to this level their synthflesh partially falls off
var/datum/species/fake_species //a species to do most of our work for us, unless we're damaged
var/list/initial_species_traits //for getting these values back for assume_disguise()
var/list/initial_inherent_traits
@@ -41,7 +41,7 @@
UnregisterSignal(H, COMSIG_MOB_SAY)
/datum/species/synth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
- if(chem.type == /datum/reagent/medicine/c2/instabitaluri)
+ if(chem.type == /datum/reagent/medicine/c2/synthflesh)
chem.expose_mob(H, TOUCH, 2 ,0) //heal a little
H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
return 1
diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm
index 1bd65fc01ab..e1ece348605 100644
--- a/code/modules/mob/living/carbon/human/species_types/vampire.dm
+++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm
@@ -2,7 +2,7 @@
name = "Vampire"
id = "vampire"
default_color = "FFFFFF"
- species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,DRINKSBLOOD)
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,DRINKSBLOOD, HAS_FLESH, HAS_BONE)
inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH)
inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID
default_features = list("mcolor" = "FFF", "tail_human" = "None", "ears" = "None", "wings" = "None")
diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm
index 31f2b715a18..2ebf0c5f729 100644
--- a/code/modules/mob/living/carbon/human/species_types/zombies.dm
+++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm
@@ -7,7 +7,7 @@
say_mod = "moans"
sexes = 0
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/zombie
- species_traits = list(NOBLOOD,NOZOMBIE,NOTRANSSTING)
+ species_traits = list(NOBLOOD,NOZOMBIE,NOTRANSSTING, HAS_FLESH, HAS_BONE)
inherent_traits = list(TRAIT_NOMETABOLISM,TRAIT_TOXIMMUNE,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH,TRAIT_NOCLONELOSS)
inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID
mutanttongue = /obj/item/organ/tongue/zombie
@@ -46,7 +46,7 @@
/datum/species/zombie/infectious/spec_stun(mob/living/carbon/human/H,amount)
. = min(20, amount)
-/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
. = ..()
if(.)
regen_cooldown = world.time + REGENERATION_DELAY
@@ -64,9 +64,9 @@
C.heal_overall_damage(heal_amt,heal_amt)
C.adjustToxLoss(-heal_amt)
for(var/i in C.all_wounds)
- var/datum/wound/W = i
- if(prob(4-W.severity))
- W.remove_wound()
+ var/datum/wound/iter_wound = i
+ if(prob(4-iter_wound.severity))
+ iter_wound.remove_wound()
if(!C.InCritical() && prob(4))
playsound(C, pick(spooks), 50, TRUE, 10)
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index 07150513e2f..dfcd3a826ee 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -197,7 +197,8 @@
to_chat(src, "You have no empty hands!")
return
if(!giver.temporarilyRemoveItemFromInventory(I))
- visible_message("[src] tries to hand over [I] but it's stuck to them....", \
- " You make a fool of yourself trying to give away an item stuck to your hands")
+ visible_message("[giver] tries to hand over [I] but it's stuck to them....")
return
+ visible_message("[src] takes [I] from [giver]", \
+ "You take [I] from [giver]")
put_in_hands(I)
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index 55cf8d56a1a..18cb1c5b236 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -14,7 +14,7 @@
*
* Returns TRUE if damage applied
*/
-/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMGE, damage, damagetype, def_zone)
var/hit_percent = (100-blocked)/100
if(!damage || (!forced && hit_percent <= 0))
@@ -263,7 +263,7 @@
update_stamina()
/// damage ONE external organ, organ gets randomly selected from damaged ones.
-/mob/living/proc/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status, check_armor = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/proc/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status, check_armor = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
adjustBruteLoss(brute, FALSE) //zero as argument for no instant health update
adjustFireLoss(burn, FALSE)
adjustStaminaLoss(stamina, FALSE)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index dace9d9c70e..c1c5b14182d 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -677,41 +677,56 @@
return
/mob/living/proc/makeTrail(turf/target_turf, turf/start, direction)
- if(!has_gravity())
+ if(!has_gravity() || !isturf(start) || !blood_volume)
return
- var/blood_exists = FALSE
- for(var/obj/effect/decal/cleanable/trail_holder/C in start) //checks for blood splatter already on the floor
- blood_exists = TRUE
- if(isturf(start))
- var/trail_type = getTrail()
- if(trail_type)
- var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
- if(blood_volume && blood_volume > max(BLOOD_VOLUME_NORMAL*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
- blood_volume = max(blood_volume - max(1, brute_ratio * 2), 0) //that depends on our brute damage.
- var/newdir = get_dir(target_turf, start)
- if(newdir != direction)
- newdir = newdir | direction
- if(newdir == 3) //N + S
- newdir = NORTH
- else if(newdir == 12) //E + W
- newdir = EAST
- if((newdir in GLOB.cardinals) && (prob(50)))
- newdir = turn(get_dir(target_turf, start), 180)
- if(!blood_exists)
- new /obj/effect/decal/cleanable/trail_holder(start, get_static_viruses())
+ var/blood_exists = locate(/obj/effect/decal/cleanable/trail_holder) in start
- for(var/obj/effect/decal/cleanable/trail_holder/TH in start)
- if((!(newdir in TH.existing_dirs) || trail_type == "trails_1" || trail_type == "trails_2") && TH.existing_dirs.len <= 16) //maximum amount of overlays is 16 (all light & heavy directions filled)
- TH.existing_dirs += newdir
- TH.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
- TH.transfer_mob_blood_dna(src)
+ var/trail_type = getTrail()
+ if(!trail_type)
+ return
+
+ var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
+ if(blood_volume < max(BLOOD_VOLUME_NORMAL*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
+ return
+
+ var/bleed_amount = bleedDragAmount()
+ blood_volume = max(blood_volume - bleed_amount, 0) //that depends on our brute damage.
+ var/newdir = get_dir(target_turf, start)
+ if(newdir != direction)
+ newdir = newdir | direction
+ if(newdir == (NORTH|SOUTH))
+ newdir = NORTH
+ else if(newdir == (EAST|WEST))
+ newdir = EAST
+ if((newdir in GLOB.cardinals) && (prob(50)))
+ newdir = turn(get_dir(target_turf, start), 180)
+ if(!blood_exists)
+ new /obj/effect/decal/cleanable/trail_holder(start, get_static_viruses())
+
+ for(var/obj/effect/decal/cleanable/trail_holder/TH in start)
+ if((!(newdir in TH.existing_dirs) || trail_type == "trails_1" || trail_type == "trails_2") && TH.existing_dirs.len <= 16) //maximum amount of overlays is 16 (all light & heavy directions filled)
+ TH.existing_dirs += newdir
+ TH.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
+ TH.transfer_mob_blood_dna(src)
/mob/living/carbon/human/makeTrail(turf/T)
if((NOBLOOD in dna.species.species_traits) || !is_bleeding() || bleedsuppress)
return
..()
+///Returns how much blood we're losing from being dragged a tile, from [mob/living/proc/makeTrail]
+/mob/living/proc/bleedDragAmount()
+ var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
+ return max(1, brute_ratio * 2)
+
+/mob/living/carbon/bleedDragAmount()
+ var/bleed_amount = 0
+ for(var/i in all_wounds)
+ var/datum/wound/iter_wound = i
+ bleed_amount += iter_wound.drag_bleed_amount()
+ return bleed_amount
+
/mob/living/proc/getTrail()
if(getBruteLoss() < 300)
return pick("ltrails_1", "ltrails_2")
@@ -927,6 +942,8 @@
/mob/living/singularity_pull(S, current_size)
..()
+ if(move_resist == INFINITY)
+ return
if(current_size >= STAGE_SIX) //your puny magboots/wings/whatever will not save you against supermatter singularity
throw_at(S, 14, 3, src, TRUE)
else if(!src.mob_negates_gravity())
@@ -1150,11 +1167,6 @@
//Mobs on Fire end
-//Washing
-/mob/living/washed(var/atom/washer)
- . = ..()
- SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
-
// used by secbot and monkeys Crossed
/mob/living/proc/knockOver(var/mob/living/carbon/C)
if(C.key) //save us from monkey hordes
@@ -1294,14 +1306,24 @@
SSmobs.clients_by_zlevel[registered_z] -= src
if (client)
if (new_z)
+ //Figure out how many clients were here before
+ var/oldlen = SSmobs.clients_by_zlevel[new_z].len
SSmobs.clients_by_zlevel[new_z] += src
for (var/I in length(SSidlenpcpool.idle_mobs_by_zlevel[new_z]) to 1 step -1) //Backwards loop because we're removing (guarantees optimal rather than worst-case performance), it's fine to use .len here but doesn't compile on 511
var/mob/living/simple_animal/SA = SSidlenpcpool.idle_mobs_by_zlevel[new_z][I]
- if (SA && get_dist(get_turf(src), get_turf(SA)) < MAX_SIMPLEMOB_WAKEUP_RANGE)
- SA.consider_wakeup() // Ask the mob if it wants to turn on it's AI
+ if (SA)
+ if(oldlen == 0)
+ //Start AI idle if nobody else was on this z level before (mobs will switch off when this is the case)
+ SA.toggle_ai(AI_IDLE)
+
+ //If they are also within a close distance ask the AI if it wants to wake up
+ if(get_dist(get_turf(src), get_turf(SA)) < MAX_SIMPLEMOB_WAKEUP_RANGE)
+ SA.consider_wakeup() // Ask the mob if it wants to turn on it's AI
+ //They should clean up in destroy, but often don't so we get them here
else
SSidlenpcpool.idle_mobs_by_zlevel[new_z] -= SA
+
registered_z = new_z
else
registered_z = null
@@ -1504,7 +1526,7 @@
///Checks if the user is incapacitated or on cooldown.
/mob/living/proc/can_look_up()
- return !((next_move > world.time) || incapacitated(ignore_restraints = TRUE))
+ return !(incapacitated(ignore_restraints = TRUE))
/**
* look_up Changes the perspective of the mob to any openspace turf above the mob
@@ -1513,27 +1535,91 @@
*
*/
/mob/living/proc/look_up()
-
if(client.perspective != MOB_PERSPECTIVE) //We are already looking up.
stop_look_up()
- return
if(!can_look_up())
return
+ changeNext_move(CLICK_CD_LOOK_UP)
+ RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, .proc/stop_look_up) //We stop looking up if we move.
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/start_look_up) //We start looking again after we move.
+ start_look_up()
+
+/mob/living/proc/start_look_up()
var/turf/ceiling = get_step_multiz(src, UP)
if(!ceiling) //We are at the highest z-level.
to_chat(src, "You can't see through the ceiling above you.")
return
else if(!istransparentturf(ceiling)) //There is no turf we can look through above us
- to_chat(src, "You can't see through the floor above you.")
- return
+ var/turf/front_hole = get_step(ceiling, dir)
+ if(istransparentturf(front_hole))
+ ceiling = front_hole
+ else
+ var/list/checkturfs = block(locate(x-1,y-1,ceiling.z),locate(x+1,y+1,ceiling.z))-ceiling-front_hole //Try find hole near of us
+ for(var/turf/checkhole in checkturfs)
+ if(istransparentturf(checkhole))
+ ceiling = checkhole
+ break
+ if(!istransparentturf(ceiling))
+ to_chat(src, "You can't see through the floor above you.")
+ return
- changeNext_move(CLICK_CD_LOOK_UP)
reset_perspective(ceiling)
- RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, .proc/stop_look_up) //We stop looking up if we move.
/mob/living/proc/stop_look_up()
reset_perspective()
+
+/mob/living/proc/end_look_up()
+ stop_look_up()
UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE)
+ UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
+
+/**
+ * look_down Changes the perspective of the mob to any openspace turf below the mob
+ *
+ * This also checks if an openspace turf is below the mob before looking down or resets the perspective if already looking up
+ *
+ */
+/mob/living/proc/look_down()
+ if(client.perspective != MOB_PERSPECTIVE) //We are already looking down.
+ stop_look_down()
+ if(!can_look_up()) //if we cant look up, we cant look down.
+ return
+ changeNext_move(CLICK_CD_LOOK_UP)
+ RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, .proc/stop_look_down) //We stop looking down if we move.
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/start_look_down) //We start looking again after we move.
+ start_look_down()
+
+/mob/living/proc/start_look_down()
+ var/turf/floor = get_turf(src)
+ var/turf/lower_level = get_step_multiz(floor, DOWN)
+ if(!lower_level) //We are at the lowest z-level.
+ to_chat(src, "You can't see through the floor below you.")
+ return
+ else if(!istransparentturf(floor)) //There is no turf we can look through below us
+ var/turf/front_hole = get_step(floor, dir)
+ if(istransparentturf(front_hole))
+ floor = front_hole
+ lower_level = get_step_multiz(front_hole, DOWN)
+ else
+ var/list/checkturfs = block(locate(x-1,y-1,z),locate(x+1,y+1,z))-floor //Try find hole near of us
+ for(var/turf/checkhole in checkturfs)
+ if(istransparentturf(checkhole))
+ floor = checkhole
+ lower_level = get_step_multiz(checkhole, DOWN)
+ break
+ if(!istransparentturf(floor))
+ to_chat(src, "You can't see through the floor below you.")
+ return
+
+ reset_perspective(lower_level)
+
+/mob/living/proc/stop_look_down()
+ reset_perspective()
+
+/mob/living/proc/end_look_down()
+ stop_look_down()
+ UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE)
+ UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
/mob/living/set_stat(new_stat)
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index b634bd42822..4748489e0c8 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -51,7 +51,7 @@
var/armor = run_armor_check(def_zone, P.flag, "","",P.armour_penetration)
var/on_hit_state = P.on_hit(src, armor)
if(!P.nodamage && on_hit_state != BULLET_ACT_BLOCK)
- apply_damage(P.damage, P.damage_type, def_zone, armor, wound_bonus=P.wound_bonus, bare_wound_bonus=P.bare_wound_bonus, sharpness=P.sharpness)
+ apply_damage(P.damage, P.damage_type, def_zone, armor, wound_bonus=P.wound_bonus, bare_wound_bonus=P.bare_wound_bonus, sharpness = P.sharpness)
apply_effects(P.stun, P.knockdown, P.unconscious, P.irradiate, P.slur, P.stutter, P.eyeblur, P.drowsy, armor, P.stamina, P.jitter, P.paralyze, P.immobilize)
if(P.dismemberment)
check_projectile_dismemberment(P, def_zone)
@@ -80,14 +80,16 @@
dtype = I.damtype
if(!blocked)
- visible_message("[src] is hit by [I]!", \
- "You're hit by [I]!")
- if(!I.throwforce)
- return
- var/armor = run_armor_check(zone, "melee", "Your armor has protected your [parse_zone(zone)].", "Your armor has softened hit to your [parse_zone(zone)].",I.armour_penetration)
- apply_damage(I.throwforce, dtype, zone, armor, sharpness=I.sharpness)
if(I.thrownby)
log_combat(I.thrownby, src, "threw and hit", I)
+ if(!nosell_hit)
+ visible_message("[src] is hit by [I]!", \
+ "You're hit by [I]!")
+ if(!I.throwforce)
+ return
+ var/armor = run_armor_check(zone, "melee", "Your armor has protected your [parse_zone(zone)].", "Your armor has softened hit to your [parse_zone(zone)].",I.armour_penetration)
+ apply_damage(I.throwforce, dtype, zone, armor, sharpness=I.get_sharpness(), wound_bonus=(nosell_hit * CANT_WOUND))
+
else
return 1
else
diff --git a/code/modules/mob/living/silicon/damage_procs.dm b/code/modules/mob/living/silicon/damage_procs.dm
index 15bb5f2f1fa..1a6e3e16492 100644
--- a/code/modules/mob/living/silicon/damage_procs.dm
+++ b/code/modules/mob/living/silicon/damage_procs.dm
@@ -1,5 +1,5 @@
-/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
var/hit_percent = (100-blocked)/100
if((!damage || (!forced && hit_percent <= 0)))
return 0
@@ -9,9 +9,6 @@
adjustBruteLoss(damage_amount, forced = forced)
if(BURN)
adjustFireLoss(damage_amount, forced = forced)
- if(OXY)
- if(damage < 0 || forced) //we shouldn't be taking oxygen damage through this proc, but we'll let it heal.
- adjustOxyLoss(damage_amount, forced = forced)
return 1
@@ -30,14 +27,26 @@
/mob/living/silicon/setCloneLoss(amount, updating_health = TRUE, forced = FALSE)
return FALSE
-/mob/living/silicon/adjustStaminaLoss(amount, updating_health = TRUE, forced = FALSE)//immune to stamina damage.
+/mob/living/silicon/adjustStaminaLoss(amount, updating_health = TRUE, forced = FALSE) //immune to stamina damage.
return FALSE
/mob/living/silicon/setStaminaLoss(amount, updating_health = TRUE)
return FALSE
-/mob/living/silicon/adjustOrganLoss(slot, amount, maximum = 500)
+/mob/living/silicon/adjustOrganLoss(slot, amount, maximum = 500) //immune to organ damage (no organs, duh)
return FALSE
/mob/living/silicon/setOrganLoss(slot, amount)
return FALSE
+
+/mob/living/silicon/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE) //immune to oxygen damage
+ if(istype(src, /mob/living/silicon/ai)) //ais are snowflakes and use oxyloss for being in AI cards and having no battery
+ return ..()
+
+ return FALSE
+
+/mob/living/silicon/setOxyLoss(amount, updating_health = TRUE, forced = FALSE)
+ if(istype(src, /mob/living/silicon/ai)) //ditto
+ return ..()
+
+ return FALSE
diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm
index cd14bdfbae0..487cb4f93ff 100644
--- a/code/modules/mob/living/silicon/laws.dm
+++ b/code/modules/mob/living/silicon/laws.dm
@@ -8,7 +8,7 @@
/mob/living/silicon/proc/deadchat_lawchange()
var/list/the_laws = laws.get_law_list(include_zeroth = TRUE)
var/lawtext = the_laws.Join(" ")
- deadchat_broadcast("'s laws were changed.View", "[src]", follow_target=src, message_type=DEADCHAT_LAWCHANGE)
+ deadchat_broadcast("'s laws were changed.View", "[src]", follow_target=src, message_type=DEADCHAT_LAWCHANGE)
/mob/living/silicon/proc/post_lawchange(announce = TRUE)
throw_alert("newlaw", /obj/screen/alert/newlaw)
diff --git a/code/modules/mob/living/silicon/pai/pai_defense.dm b/code/modules/mob/living/silicon/pai/pai_defense.dm
index 3272173da24..e21b73ad1cd 100644
--- a/code/modules/mob/living/silicon/pai/pai_defense.dm
+++ b/code/modules/mob/living/silicon/pai/pai_defense.dm
@@ -86,15 +86,6 @@
/mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE)
return take_holo_damage(amount)
-/mob/living/silicon/pai/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE)
- return FALSE
-
-/mob/living/silicon/pai/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE)
- return FALSE
-
-/mob/living/silicon/pai/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE)
- return FALSE
-
/mob/living/silicon/pai/adjustStaminaLoss(amount, updating_health, forced = FALSE)
if(forced)
take_holo_damage(amount)
@@ -106,27 +97,3 @@
/mob/living/silicon/pai/getFireLoss()
return emittermaxhealth - emitterhealth
-
-/mob/living/silicon/pai/getToxLoss()
- return FALSE
-
-/mob/living/silicon/pai/getOxyLoss()
- return FALSE
-
-/mob/living/silicon/pai/getCloneLoss()
- return FALSE
-
-/mob/living/silicon/pai/getStaminaLoss()
- return FALSE
-
-/mob/living/silicon/pai/setCloneLoss()
- return FALSE
-
-/mob/living/silicon/pai/setStaminaLoss(amount, updating_health = TRUE)
- return FALSE
-
-/mob/living/silicon/pai/setToxLoss()
- return FALSE
-
-/mob/living/silicon/pai/setOxyLoss()
- return FALSE
diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm
index 60ac3f975cd..3e59b227521 100644
--- a/code/modules/mob/living/silicon/robot/inventory.dm
+++ b/code/modules/mob/living/silicon/robot/inventory.dm
@@ -1,215 +1,390 @@
//These procs handle putting stuff in your hand. It's probably best to use these rather than setting stuff manually
//as they handle all relevant stuff like adding it to the player's screen and such
-//Returns the thing in our active hand (whatever is in our active module-slot, in this case)
+/**
+ * Returns the thing in our active hand (whatever is in our active module-slot, in this case)
+ */
/mob/living/silicon/robot/get_active_held_item()
return module_active
+/**
+ * Parent proc - triggers when an item/module is unequipped from a cyborg.
+ */
/obj/item/proc/cyborg_unequip(mob/user)
return
-/mob/living/silicon/robot/proc/uneq_module(obj/item/O)
- if(!O)
- return 0
- O.mouse_opacity = MOUSE_OPACITY_OPAQUE
- if(istype(O, /obj/item/borg/sight))
- var/obj/item/borg/sight/S = O
- sight_mode &= ~S.sight_mode
- update_sight()
- else if(istype(O, /obj/item/storage/bag/tray/))
- SEND_SIGNAL(O, COMSIG_TRY_STORAGE_QUICK_EMPTY)
- if(client)
- client.screen -= O
- observer_screen_update(O,FALSE)
+/**
+ * Finds the first available slot and attemps to put item item_module in it.
+ *
+ * Arguments
+ * * item_module - the item being equipped to a slot.
+ */
+/mob/living/silicon/robot/proc/activate_module(obj/item/item_module)
+ if(QDELETED(item_module))
+ CRASH("activate_module called with improper item_module")
- if(module_active == O)
- module_active = null
- if(held_items[1] == O)
- inv1.icon_state = "inv1"
- held_items[1] = null
- else if(held_items[2] == O)
- inv2.icon_state = "inv2"
- held_items[2] = null
- else if(held_items[3] == O)
- inv3.icon_state = "inv3"
- held_items[3] = null
+ if(!(item_module in module.modules))
+ CRASH("activate_module called with item_module not in module.modules")
- if(O.item_flags & DROPDEL)
- O.item_flags &= ~DROPDEL //we shouldn't HAVE things with DROPDEL_1 in our modules, but better safe than runtiming horribly
-
- O.forceMove(module) //Return item to module so it appears in its contents, so it can be taken out again.
- O.cyborg_unequip(src)
-
- hud_used.update_robot_modules_display()
- return 1
-
-/mob/living/silicon/robot/proc/activate_module(obj/item/O)
- . = FALSE
- if(!(O in module.modules))
- return
- if(activated(O))
+ if(activated(item_module))
to_chat(src, "That module is already activated.")
- return
- if(!held_items[1])
- held_items[1] = O
- O.screen_loc = inv1.screen_loc
- . = TRUE
- else if(!held_items[2])
- held_items[2] = O
- O.screen_loc = inv2.screen_loc
- . = TRUE
- else if(!held_items[3])
- held_items[3] = O
- O.screen_loc = inv3.screen_loc
- . = TRUE
- else
- to_chat(src, "You need to disable a module first!")
- if(.)
- O.equipped(src, ITEM_SLOT_HANDS)
- O.mouse_opacity = initial(O.mouse_opacity)
- O.layer = ABOVE_HUD_LAYER
- O.plane = ABOVE_HUD_PLANE
- observer_screen_update(O,TRUE)
- O.forceMove(src)
- if(istype(O, /obj/item/borg/sight))
- var/obj/item/borg/sight/S = O
- sight_mode |= S.sight_mode
- update_sight()
+ return FALSE
+
+ if(disabled_modules & BORG_MODULE_ALL_DISABLED)
+ to_chat(src, "All modules are disabled!")
+ return FALSE
+
+ /// What's the first free slot for the borg?
+ var/first_free_slot = !held_items[1] ? 1 : (!held_items[2] ? 2 : (!held_items[3] ? 3 : null))
+
+ if(!first_free_slot || is_invalid_module_number(first_free_slot))
+ to_chat(src, "Deactivate a module first!")
+ return FALSE
+
+ return equip_module_to_slot(item_module, first_free_slot)
+
+/**
+ * Is passed an item and a module slot. Equips the item to that borg slot.
+ *
+ * Arguments
+ * * item_module - the item being equipped to a slot
+ * * module_num - the slot number being equipped to.
+ */
+/mob/living/silicon/robot/proc/equip_module_to_slot(obj/item/item_module, module_num)
+ switch(module_num)
+ if(1)
+ item_module.screen_loc = inv1.screen_loc
+ if(2)
+ item_module.screen_loc = inv2.screen_loc
+ if(3)
+ item_module.screen_loc = inv3.screen_loc
+
+ held_items[module_num] = item_module
+ item_module.equipped(src, ITEM_SLOT_HANDS)
+ item_module.mouse_opacity = initial(item_module.mouse_opacity)
+ item_module.layer = ABOVE_HUD_LAYER
+ item_module.plane = ABOVE_HUD_PLANE
+ item_module.forceMove(src)
+
+ if(istype(item_module, /obj/item/borg/sight))
+ var/obj/item/borg/sight/borg_sight = item_module
+ sight_mode |= borg_sight.sight_mode
+ update_sight()
+
+ observer_screen_update(item_module, TRUE)
+ return TRUE
+
+/**
+ * Unequips item item_module from slot module_num. Deletes it if delete_after = TRUE.
+ *
+ * Arguments
+ * * item_module - the item being unequipped
+ * * module_num - the slot number being unequipped.
+ */
+/mob/living/silicon/robot/proc/unequip_module_from_slot(obj/item/item_module, module_num)
+ if(QDELETED(item_module))
+ CRASH("unequip_module_from_slot called with improper item_module")
+
+ if(!(item_module in module.modules))
+ CRASH("unequip_module_from_slot called with item_module not in module.modules")
+
+ item_module.mouse_opacity = MOUSE_OPACITY_OPAQUE
+
+ if(istype(item_module, /obj/item/storage/bag/tray/))
+ SEND_SIGNAL(item_module, COMSIG_TRY_STORAGE_QUICK_EMPTY)
+ if(istype(item_module, /obj/item/borg/sight))
+ var/obj/item/borg/sight/borg_sight = item_module
+ sight_mode &= ~borg_sight.sight_mode
+ update_sight()
+
+ if(client)
+ client.screen -= item_module
+
+ if(module_active == item_module)
+ module_active = null
+
+ switch(module_num)
+ if(1)
+ if(!(disabled_modules & BORG_MODULE_ALL_DISABLED))
+ inv1.icon_state = initial(inv1.icon_state)
+ if(2)
+ if(!(disabled_modules & BORG_MODULE_TWO_DISABLED))
+ inv2.icon_state = initial(inv2.icon_state)
+ if(3)
+ if(!(disabled_modules & BORG_MODULE_THREE_DISABLED))
+ inv3.icon_state = initial(inv3.icon_state)
+
+ if(item_module.item_flags & DROPDEL)
+ item_module.item_flags &= ~DROPDEL //we shouldn't HAVE things with DROPDEL_1 in our modules, but better safe than runtiming horribly
+
+ held_items[module_num] = null
+ item_module.cyborg_unequip(src)
+ item_module.forceMove(module) //Return item to module so it appears in its contents, so it can be taken out again.
+
+ observer_screen_update(item_module, FALSE)
+ hud_used.update_robot_modules_display()
+ return TRUE
+
+/**
+ * Breaks the slot number, changing the icon.
+ *
+ * Arguments
+ * * module_num - the slot number being repaired.
+ */
+/mob/living/silicon/robot/proc/break_cyborg_slot(module_num)
+ if(is_invalid_module_number(module_num, TRUE))
+ return FALSE
+
+ if(held_items[module_num]) //If there's a held item, unequip it first.
+ if(!unequip_module_from_slot(held_items[module_num], module_num)) //If we fail to unequip it, then don't continue
+ return FALSE
+
+ switch(module_num)
+ if(1)
+ if(disabled_modules & BORG_MODULE_ALL_DISABLED)
+ return FALSE
+
+ inv1.icon_state = "[initial(inv1.icon_state)] +b"
+ disabled_modules |= BORG_MODULE_ALL_DISABLED
+
+ playsound(src, 'sound/machines/warning-buzzer.ogg', 75, TRUE, TRUE)
+ audible_message("[src] sounds an alarm! \"CRITICAL ERROR: ALL modules OFFLINE.\"")
+ to_chat(src, "CRITICAL ERROR: ALL modules OFFLINE.")
+
+ if(2)
+ if(disabled_modules & BORG_MODULE_TWO_DISABLED)
+ return FALSE
+
+ inv2.icon_state = "[initial(inv2.icon_state)] +b"
+ disabled_modules |= BORG_MODULE_TWO_DISABLED
+
+ playsound(src, 'sound/machines/warning-buzzer.ogg', 60, TRUE, TRUE)
+ audible_message("[src] sounds an alarm! \"SYSTEM ERROR: Module [module_num] OFFLINE.\"")
+ to_chat(src, "SYSTEM ERROR: Module [module_num] OFFLINE.")
+
+ if(3)
+ if(disabled_modules & BORG_MODULE_THREE_DISABLED)
+ return FALSE
+
+ inv3.icon_state = "[initial(inv3.icon_state)] +b"
+ disabled_modules |= BORG_MODULE_THREE_DISABLED
+
+ playsound(src, 'sound/machines/warning-buzzer.ogg', 50, TRUE, TRUE)
+ audible_message("[src] sounds an alarm! \"SYSTEM ERROR: Module [module_num] OFFLINE.\"")
+ to_chat(src, "SYSTEM ERROR: Module [module_num] OFFLINE.")
+
+ return TRUE
-/mob/living/silicon/robot/proc/observer_screen_update(obj/item/I,add = TRUE)
+/**
+ * Breaks all of a cyborg's slots.
+ */
+/mob/living/silicon/robot/proc/break_all_cyborg_slots()
+ for(var/cyborg_slot in 1 to 3)
+ break_cyborg_slot(cyborg_slot)
+
+/**
+ * Repairs the slot number, updating the icon.
+ *
+ * Arguments
+ * * module_num - the module number being repaired.
+ */
+/mob/living/silicon/robot/proc/repair_cyborg_slot(module_num)
+ if(is_invalid_module_number(module_num, TRUE))
+ return FALSE
+
+ switch(module_num)
+ if(1)
+ if(!(disabled_modules & BORG_MODULE_ALL_DISABLED))
+ return FALSE
+
+ inv1.icon_state = initial(inv1.icon_state)
+ disabled_modules &= ~BORG_MODULE_ALL_DISABLED
+ if(2)
+ if(!(disabled_modules & BORG_MODULE_TWO_DISABLED))
+ return FALSE
+
+ inv2.icon_state = initial(inv2.icon_state)
+ disabled_modules &= ~BORG_MODULE_TWO_DISABLED
+ if(3)
+ if(!(disabled_modules & BORG_MODULE_THREE_DISABLED))
+ return FALSE
+
+ inv3.icon_state = initial(inv3.icon_state)
+ disabled_modules &= ~BORG_MODULE_THREE_DISABLED
+
+ to_chat(src, "ERROR CLEARED: Module [module_num] back online.")
+
+ return TRUE
+
+/**
+ * Repairs all slots. Unbroken slots are unaffected.
+ */
+/mob/living/silicon/robot/proc/repair_all_cyborg_slots()
+ for(var/cyborg_slot in 1 to 3)
+ repair_cyborg_slot(cyborg_slot)
+
+/**
+ * Updates the observers's screens with cyborg itemss.
+ * Arguments
+ * * item_module - the item being added or removed from the screen
+ * * add - whether or not the item is being added, or removed.
+ */
+/mob/living/silicon/robot/proc/observer_screen_update(obj/item/item_module, add = TRUE)
if(observers && observers.len)
for(var/M in observers)
var/mob/dead/observe = M
if(observe.client && observe.client.eye == src)
if(add)
- observe.client.screen += I
+ observe.client.screen += item_module
else
- observe.client.screen -= I
+ observe.client.screen -= item_module
else
observers -= observe
if(!observers.len)
observers = null
break
+/**
+ * Unequips the active held item, if there is one.
+ */
/mob/living/silicon/robot/proc/uneq_active()
- uneq_module(module_active)
+ if(module_active)
+ unequip_module_from_slot(module_active, get_selected_module())
+/**
+ * Unequips all held items.
+ */
/mob/living/silicon/robot/proc/uneq_all()
- for(var/obj/item/I in held_items)
- uneq_module(I)
+ for(var/cyborg_slot in 1 to 3)
+ if(!held_items[cyborg_slot])
+ continue
+ unequip_module_from_slot(held_items[cyborg_slot], cyborg_slot)
-/mob/living/silicon/robot/proc/activated(obj/item/O)
- if(O in held_items)
+/**
+ * Checks if the item is currently in a slot.
+ *
+ * If the item is found in a slot, this returns TRUE. Otherwise, it returns FALSE
+ * Arguments
+ * * item_module - the item being checked
+ */
+/mob/living/silicon/robot/proc/activated(obj/item/item_module)
+ if(item_module in held_items)
return TRUE
return FALSE
-//Helper procs for cyborg modules on the UI.
-//These are hackish but they help clean up code elsewhere.
+/**
+ * Checks if the provided module number is a valid number.
+ *
+ * If the number is between 1 and 3 (if check_all_slots is true) or between 1 and the number of disabled
+ * modules (if check_all_slots is false), then it returns FALSE. Otherwise, it returns TRUE.
+ * Arguments
+ * * module_num - the passed module num that is checked for validity.
+ * * check_all_slots - TRUE = the proc checks all slots | FALSE = the proc only checks un-disabled slots
+ */
+/mob/living/silicon/robot/proc/is_invalid_module_number(module_num, check_all_slots = FALSE)
+ if(!module_num)
+ return TRUE
-//module_selected(module) - Checks whether the module slot specified by "module" is currently selected.
-/mob/living/silicon/robot/proc/module_selected(module) //Module is 1-3
- return module == get_selected_module()
+ /// The number of module slots we're checking
+ var/max_number = 3
+ if(!check_all_slots)
+ if(disabled_modules & BORG_MODULE_ALL_DISABLED)
+ max_number = 0
+ else if(disabled_modules & BORG_MODULE_TWO_DISABLED)
+ max_number = 1
+ else if(disabled_modules & BORG_MODULE_THREE_DISABLED)
+ max_number = 2
-//module_active(module) - Checks whether there is a module active in the slot specified by "module".
-/mob/living/silicon/robot/proc/module_active(module) //Module is 1-3
- if(module < 1 || module > 3)
- return FALSE
+ return module_num < 1 || module_num > max_number
- if(LAZYLEN(held_items) >= module)
- if(held_items[module])
- return TRUE
- return FALSE
-
-//get_selected_module() - Returns the slot number of the currently selected module. Returns 0 if no modules are selected.
+/**
+ * Returns the slot number of the selected module, or zero if no modules are selected.
+ */
/mob/living/silicon/robot/proc/get_selected_module()
if(module_active)
return held_items.Find(module_active)
return 0
-//select_module(module) - Selects the module slot specified by "module"
-/mob/living/silicon/robot/proc/select_module(module) //Module is 1-3
- if(module < 1 || module > 3)
- return
+/**
+ * Selects the module in the slot module_num.
+ * Arguments
+ * * module_num - the slot number being selected
+ */
+/mob/living/silicon/robot/proc/select_module(module_num)
+ if(is_invalid_module_number(module_num) || !held_items[module_num]) //If the slot number is invalid, or there's nothing there, we have nothing to equip
+ return FALSE
- if(!module_active(module))
- return
-
- switch(module)
+ switch(module_num)
if(1)
- if(module_active != held_items[module])
- inv1.icon_state = "inv1 +a"
- inv2.icon_state = "inv2"
- inv3.icon_state = "inv3"
+ if(module_active != held_items[module_num])
+ inv1.icon_state = "[initial(inv1.icon_state)] +a"
if(2)
- if(module_active != held_items[module])
- inv1.icon_state = "inv1"
- inv2.icon_state = "inv2 +a"
- inv3.icon_state = "inv3"
+ if(module_active != held_items[module_num])
+ inv2.icon_state = "[initial(inv2.icon_state)] +a"
if(3)
- if(module_active != held_items[module])
- inv1.icon_state = "inv1"
- inv2.icon_state = "inv2"
- inv3.icon_state = "inv3 +a"
- module_active = held_items[module]
+ if(module_active != held_items[module_num])
+ inv3.icon_state = "[initial(inv3.icon_state)] +a"
+ module_active = held_items[module_num]
+ return TRUE
-//deselect_module(module) - Deselects the module slot specified by "module"
-/mob/living/silicon/robot/proc/deselect_module(module) //Module is 1-3
- if(module < 1 || module > 3)
- return
-
- if(!module_active(module))
- return
-
- switch(module)
+/**
+ * Deselects the module in the slot module_num.
+ * Arguments
+ * * module_num - the slot number being de-selected
+ */
+/mob/living/silicon/robot/proc/deselect_module(module_num)
+ switch(module_num)
if(1)
- if(module_active == held_items[module])
- inv1.icon_state = "inv1"
+ if(module_active == held_items[module_num])
+ inv1.icon_state = initial(inv1.icon_state)
if(2)
- if(module_active == held_items[module])
- inv2.icon_state = "inv2"
+ if(module_active == held_items[module_num])
+ inv2.icon_state = initial(inv2.icon_state)
if(3)
- if(module_active == held_items[module])
- inv3.icon_state = "inv3"
+ if(module_active == held_items[module_num])
+ inv3.icon_state = initial(inv3.icon_state)
module_active = null
+ return TRUE
-//toggle_module(module) - Toggles the selection of the module slot specified by "module".
-/mob/living/silicon/robot/proc/toggle_module(module) //Module is 1-3
- if(module < 1 || module > 3)
- return
+/**
+ * Toggles selection of the module in the slot module_num.
+ * Arguments
+ * * module_num - the slot number being toggled
+ */
+/mob/living/silicon/robot/proc/toggle_module(module_num)
+ if(is_invalid_module_number(module_num))
+ return FALSE
- if(module_selected(module))
- deselect_module(module)
- else
- if(module_active(module))
- select_module(module)
- else
- deselect_module(get_selected_module()) //If we can't do select anything, at least deselect the current module.
- return
+ if(module_num == get_selected_module())
+ deselect_module(module_num)
+ return TRUE
-//cycle_modules() - Cycles through the list of selected modules.
+ if(module_active != held_items[module_num])
+ deselect_module(get_selected_module())
+
+ return select_module(module_num)
+
+/**
+ * Cycles through the list of enabled modules, deselecting the current one and selecting the next one.
+ */
/mob/living/silicon/robot/proc/cycle_modules()
var/slot_start = get_selected_module()
+ var/slot_num
if(slot_start)
deselect_module(slot_start) //Only deselect if we have a selected slot.
-
- var/slot_num
- if(slot_start == 0)
+ slot_num = slot_start + 1
+ else
slot_num = 1
slot_start = 4
- else
- slot_num = slot_start + 1
while(slot_num != slot_start) //If we wrap around without finding any free slots, just give up.
- if(module_active(slot_num))
- select_module(slot_num)
+ if(select_module(slot_num))
return
slot_num++
if(slot_num > 4) // not >3 otherwise cycling with just one item on module 3 wouldn't work
slot_num = 1 //Wrap around.
-
-
/mob/living/silicon/robot/swap_hand()
cycle_modules()
diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm
index 910a2a6fea0..afad167f231 100644
--- a/code/modules/mob/living/silicon/robot/life.dm
+++ b/code/modules/mob/living/silicon/robot/life.dm
@@ -4,7 +4,6 @@
return
..()
- adjustOxyLoss(-10) //we're a robot!
handle_robot_hud_updates()
handle_robot_cell()
@@ -100,4 +99,3 @@
mobility_flags = MOBILITY_FLAGS_DEFAULT
update_transform()
update_action_buttons_icon()
-
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 57fa699c111..70f46207645 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -24,6 +24,8 @@
var/mob/living/silicon/ai/mainframe = null
var/datum/action/innate/undeployment/undeployment_action = new
+ /// the last health before updating - to check net change in health
+ var/previous_health
//Hud stuff
var/obj/screen/inv1 = null
@@ -41,6 +43,9 @@
var/obj/item/module_active = null
held_items = list(null, null, null) //we use held_items for the module holding, because that makes sense to do!
+ /// For checking which modules are disabled or not.
+ var/disabled_modules
+
var/mutable_appearance/eye_lights
var/mob/living/silicon/ai/connected_ai = null
@@ -110,6 +115,8 @@
ident = rand(1, 999)
+ previous_health = health
+
if(ispath(cell))
cell = new cell(src)
@@ -220,7 +227,6 @@
module.transform_to(modulelist[input_module])
-
/mob/living/silicon/robot/proc/updatename(client/C)
if(shell)
return
@@ -447,11 +453,10 @@
/mob/living/silicon/robot/proc/self_destruct()
if(emagged)
- if(mmi)
- qdel(mmi)
- explosion(src.loc,1,2,4,flame_range = 2)
+ QDEL_NULL(mmi)
+ explosion(loc,1,2,4,flame_range = 2)
else
- explosion(src.loc,-1,0,2)
+ explosion(loc,-1,0,2)
gib()
/mob/living/silicon/robot/proc/UnlinkSelf()
@@ -705,21 +710,30 @@
/mob/living/silicon/robot/updatehealth()
..()
- if(health < maxHealth*0.5) //Gradual break down of modules as more damage is sustained
- if(uneq_module(held_items[3]))
- playsound(loc, 'sound/machines/warning-buzzer.ogg', 50, TRUE, TRUE)
- audible_message("[src] sounds an alarm! \"SYSTEM ERROR: Module 3 OFFLINE.\"")
- to_chat(src, "SYSTEM ERROR: Module 3 OFFLINE.")
- if(health < 0)
- if(uneq_module(held_items[2]))
- audible_message("[src] sounds an alarm! \"SYSTEM ERROR: Module 2 OFFLINE.\"")
- to_chat(src, "SYSTEM ERROR: Module 2 OFFLINE.")
- playsound(loc, 'sound/machines/warning-buzzer.ogg', 60, TRUE, TRUE)
- if(health < -maxHealth*0.5)
- if(uneq_module(held_items[1]))
- audible_message("[src] sounds an alarm! \"CRITICAL ERROR: All modules OFFLINE.\"")
- to_chat(src, "CRITICAL ERROR: All modules OFFLINE.")
- playsound(loc, 'sound/machines/warning-buzzer.ogg', 75, TRUE, TRUE)
+
+ /// the current percent health of the robot (-1 to 1)
+ var/percent_hp = health/maxHealth
+ if(health <= previous_health) //if change in health is negative (we're losing hp)
+ if(percent_hp <= 0.5)
+ break_cyborg_slot(3)
+
+ if(percent_hp <= 0)
+ break_cyborg_slot(2)
+
+ if(percent_hp <= -0.5)
+ break_cyborg_slot(1)
+
+ else //if change in health is positive (we're gaining hp)
+ if(percent_hp >= 0.5)
+ repair_cyborg_slot(3)
+
+ if(percent_hp >= 0)
+ repair_cyborg_slot(2)
+
+ if(percent_hp >= -0.5)
+ repair_cyborg_slot(1)
+
+ previous_health = health
/mob/living/silicon/robot/update_sight()
if(!client)
@@ -968,6 +982,8 @@
mainframe.diag_hud_set_deployed()
if(mainframe.laws)
mainframe.laws.show_laws(mainframe) //Always remind the AI when switching
+ if(mainframe.eyeobj)
+ mainframe.eyeobj.setLoc(loc)
mainframe = null
/mob/living/silicon/robot/attack_ai(mob/user)
@@ -980,7 +996,7 @@
cell = null
/mob/living/silicon/robot/mouse_buckle_handling(mob/living/M, mob/living/user)
- if(can_buckle && istype(M) && !(M in buckled_mobs) && ((user!=src)||(a_intent != INTENT_HARM)))
+ if(can_buckle && isliving(user) && isliving(M) && !(M in buckled_mobs) && ((user != src) || (a_intent != INTENT_HARM)))
if(buckle_mob(M))
return TRUE
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index 358464b81be..bc872ba0608 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -18,7 +18,6 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
return
adjustBruteLoss(-30)
- updatehealth()
add_fingerprint(user)
visible_message("[user] fixes some of the dents on [src].")
return
@@ -33,8 +32,6 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
return
if (coil.use(1))
adjustFireLoss(-30)
- adjustToxLoss(-30)
- updatehealth()
user.visible_message("[user] fixes some of the burnt wires on [src].", "You fix some of the burnt wires on [src].")
else
to_chat(user, "You need more cable to repair [src]!")
@@ -268,7 +265,6 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
damage = rand(5, 35)
damage = round(damage / 2) // borgs receive half damage
adjustBruteLoss(damage)
- updatehealth()
return
@@ -399,25 +395,3 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
updatehealth()
if(prob(75) && Proj.damage > 0)
spark_system.start()
-
-
-/mob/living/silicon/robot/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE)
- . = ..()
- if(isnull(.))
- return
- if(. <= (maxHealth * 0.5))
- if(getOxyLoss() > (maxHealth * 0.5))
- ADD_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
- else if(getOxyLoss() <= (maxHealth * 0.5))
- REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
-
-
-/mob/living/silicon/robot/setOxyLoss(amount, updating_health = TRUE, forced = FALSE)
- . = ..()
- if(isnull(.))
- return
- if(. <= (maxHealth * 0.5))
- if(getOxyLoss() > (maxHealth * 0.5))
- ADD_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
- else if(getOxyLoss() <= (maxHealth * 0.5))
- REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index a9cbaf00e81..8db514ba47b 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -161,7 +161,8 @@
/obj/item/robot_module/proc/rebuild_modules() //builds the usable module list from the modules we have
var/mob/living/silicon/robot/R = loc
- var/held_modules = R.held_items.Copy()
+ var/list/held_modules = R.held_items.Copy()
+ var/active_module = R.module_active
R.uneq_all()
modules = list()
for(var/obj/item/I in basic_modules)
@@ -173,7 +174,9 @@
add_module(I, FALSE, FALSE)
for(var/i in held_modules)
if(i)
- R.activate_module(i)
+ R.equip_module_to_slot(i, held_modules.Find(i))
+ if(active_module)
+ R.select_module(held_modules.Find(active_module))
if(R.hud_used)
R.hud_used.update_robot_modules_display()
@@ -215,7 +218,7 @@
flick("[cyborg_base_icon]_transform", R)
R.notransform = TRUE
R.SetLockdown(1)
- R.anchored = TRUE
+ R.set_anchored(TRUE)
sleep(1)
for(var/i in 1 to 4)
playsound(R, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, TRUE, -1)
@@ -223,8 +226,9 @@
if(!prev_lockcharge)
R.SetLockdown(0)
R.setDir(SOUTH)
- R.anchored = FALSE
+ R.set_anchored(FALSE)
R.notransform = FALSE
+ R.updatehealth()
R.update_headlamp(FALSE, BORG_LAMP_CD_RESET)
R.notify_ai(NEW_MODULE)
if(R.hud_used)
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index be914903255..de8ea801b82 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -215,6 +215,9 @@
statelaws()
if (href_list["printlawtext"]) // this is kinda backwards
+ if (href_list["dead"] && (!isdead(usr) && !usr.client.holder)) // do not print deadchat law notice if the user is now alive
+ to_chat(usr, "You cannot view law changes that were made while you were dead.")
+ return
to_chat(usr, href_list["printlawtext"])
return
diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm
index 13c4364f73c..5068397f9cd 100644
--- a/code/modules/mob/living/silicon/silicon_defense.dm
+++ b/code/modules/mob/living/silicon/silicon_defense.dm
@@ -41,14 +41,6 @@
adjustBruteLoss(damage)
if(BURN)
adjustFireLoss(damage)
- if(TOX)
- adjustToxLoss(damage)
- if(OXY)
- adjustOxyLoss(damage)
- if(CLONE)
- adjustCloneLoss(damage)
- if(STAMINA)
- adjustStaminaLoss(damage)
/mob/living/silicon/attack_paw(mob/living/user)
return attack_hand(user)
diff --git a/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm b/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm
index 518572b1449..4fb39e54783 100644
--- a/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm
+++ b/code/modules/mob/living/simple_animal/bot/SuperBeepsky.dm
@@ -77,7 +77,7 @@
if(Adjacent(target) && isturf(target.loc)) // if right next to perp
target_lastloc = target.loc //stun_attack() can clear the target if they're dead, so this needs to be set first
stun_attack(target)
- anchored = TRUE
+ set_anchored(TRUE)
return
else // not next to perp
var/turf/olddist = get_dist(src, target)
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index 1a3afbd358d..866b5536e33 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -130,6 +130,7 @@
update_mobility()
set_light(initial(light_range))
update_icon()
+ to_chat(src, "You turned on!")
diag_hud_set_botstat()
return TRUE
@@ -138,6 +139,7 @@
update_mobility()
set_light(0)
bot_reset() //Resets an AI's call, should it exist.
+ to_chat(src, "You turned off!")
update_icon()
/mob/living/simple_animal/bot/Initialize()
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index 9ec194dce53..89fdd74fe19 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -312,18 +312,14 @@
target_types = typecacheof(target_types)
/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/A)
- if(is_cleanable(A))
+ if(ismopable(A))
icon_state = "cleanbot-c"
mode = BOT_CLEANING
var/turf/T = get_turf(A)
if(do_after(src, 1, target = T))
- SEND_SIGNAL(T, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM)
+ T.wash(CLEAN_WASH)
visible_message("[src] cleans \the [T].")
- for(var/atom/dirtything in T)
- if(is_cleanable(dirtything))
- qdel(dirtything)
-
target = null
mode = BOT_IDLE
diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm
index d825667116d..62fcac4897c 100644
--- a/code/modules/mob/living/simple_animal/bot/floorbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm
@@ -148,7 +148,7 @@
if("autotile")
autotile = !autotile
if("anchor")
- anchored = !anchored
+ set_anchored(!anchored)
if("eject")
if(specialtiles && tiletype != null)
empty_tiles()
@@ -235,7 +235,7 @@
repair(target)
else if(emagged == 2 && isfloorturf(target))
var/turf/open/floor/F = target
- anchored = TRUE
+ set_anchored(TRUE)
mode = BOT_REPAIRING
F.ReplaceWithLattice()
audible_message("[src] makes an excited booping sound.")
@@ -283,11 +283,11 @@
if(HULL_BREACH) //The most common job, patching breaches in the station's hull.
if(is_hull_breach(scan_target)) //Ensure that the targeted space turf is actually part of the station, and not random space.
result = scan_target
- anchored = TRUE //Prevent the floorbot being blown off-course while trying to reach a hull breach.
+ set_anchored(TRUE) //Prevent the floorbot being blown off-course while trying to reach a hull breach.
if(LINE_SPACE_MODE) //Space turfs in our chosen direction are considered.
if(get_dir(src, scan_target) == targetdirection)
result = scan_target
- anchored = TRUE
+ set_anchored(TRUE)
if(PLACE_TILE)
F = scan_target
if(isplatingturf(F)) //The floor must not already have a tile.
@@ -318,7 +318,7 @@
else if(!isfloorturf(target_turf))
return
if(isspaceturf(target_turf)) //If we are fixing an area not part of pure space, it is
- anchored = TRUE
+ set_anchored(TRUE)
icon_state = "[toolbox_color]floorbot-c"
visible_message("[targetdirection ? "[src] begins installing a bridge plating." : "[src] begins to repair the hole."] ")
mode = BOT_REPAIRING
@@ -333,7 +333,7 @@
var/turf/open/floor/F = target_turf
if(F.type != initial(tiletype.turf_type) && (F.broken || F.burnt || isplatingturf(F)) || F.type == (initial(tiletype.turf_type) && (F.broken || F.burnt)))
- anchored = TRUE
+ set_anchored(TRUE)
icon_state = "[toolbox_color]floorbot-c"
mode = BOT_REPAIRING
visible_message("[src] begins repairing the floor.")
@@ -344,7 +344,7 @@
F.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
if(replacetiles && F.type != initial(tiletype.turf_type) && specialtiles && !isplatingturf(F))
- anchored = TRUE
+ set_anchored(TRUE)
icon_state = "[toolbox_color]floorbot-c"
mode = BOT_REPAIRING
visible_message("[src] begins replacing the floor tiles.")
diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm
index 73feb8384a5..1c097e736df 100644
--- a/code/modules/mob/living/simple_animal/bot/honkbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm
@@ -250,7 +250,7 @@ Maintenance panel panel is [open ? "opened" : "closed"]"},
if(threatlevel >= 6)
set waitfor = 0
stun_attack(target)
- anchored = FALSE
+ set_anchored(FALSE)
target_lastloc = target.loc
return
diff --git a/code/modules/mob/living/simple_animal/bot/hygienebot.dm b/code/modules/mob/living/simple_animal/bot/hygienebot.dm
index ed4a8aba02d..4c476ef2148 100644
--- a/code/modules/mob/living/simple_animal/bot/hygienebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/hygienebot.dm
@@ -234,7 +234,7 @@ Maintenance panel is [open ? "opened" : "closed"]"}
if(emagged)
A.fire_act() //lol pranked no cleaning besides that
else
- A.washed(src)
+ A.wash(CLEAN_WASH)
diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm
index 1d0da61f1b4..fba1b774c05 100644
--- a/code/modules/mob/living/simple_animal/bot/medbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/medbot.dm
@@ -203,7 +203,7 @@
if(tech_boosters)
heal_amount = (round(tech_boosters/2,0.1)*initial(heal_amount))+initial(heal_amount) //every 2 tend wounds tech gives you an extra 100% healing, adjusting for unique branches (combo is bonus)
if(oldheal_amount < heal_amount)
- speak("Surgical Knowledge Found! Efficiency is increased by [round(heal_amount/oldheal_amount*100)]%!")
+ speak("New knowledge found! Surgical efficacy improved to [round(heal_amount/initial(heal_amount)*100)]%!")
update_controls()
return
@@ -264,9 +264,8 @@
else
messagevoice = list("Thank you!" = 'sound/voice/medbot/thank_you.ogg', "You are a good person." = 'sound/voice/medbot/youre_good.ogg')
else
- visible_message("[src] manages to writhe wiggle enough to right itself.")
+ visible_message("[src] manages to wriggle enough to right itself.")
messagevoice = list("Fuck you." = 'sound/voice/medbot/fuck_you.ogg', "Your behavior has been reported, have a nice day." = 'sound/voice/medbot/reported.ogg')
-
tipper_name = null
if(world.time > last_tipping_action_voice + 15 SECONDS)
last_tipping_action_voice = world.time
@@ -539,7 +538,8 @@
if(!treatment_method && emagged != 2) //If they don't need any of that they're probably cured!
if(C.maxHealth - C.get_organic_health() < heal_threshold)
- to_chat(src, "[C] is healthy! Your programming prevents you from injecting anyone without at least [heal_threshold] damage of any one type ([heal_threshold + 5] for oxygen damage.)")
+ to_chat(src, "[C] is healthy! Your programming prevents you from tending the wounds of anyone without at least [heal_threshold] damage of any one type ([heal_threshold + 5] for oxygen damage.)")
+
var/list/messagevoice = list("All patched up!" = 'sound/voice/medbot/patchedup.ogg',"An apple a day keeps me away." = 'sound/voice/medbot/apple.ogg',"Feel better soon!" = 'sound/voice/medbot/feelbetter.ogg')
var/message = pick(messagevoice)
speak(message)
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index 52c3fb37d89..800aea566fa 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -30,6 +30,7 @@
bot_type = MULE_BOT
model = "MULE"
bot_core_type = /obj/machinery/bot_core/mulebot
+ hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_PATH_HUD = HUD_LIST_LIST) //Diagnostic HUD views
/// unique identifier in case there are multiple mulebots.
var/id
@@ -50,6 +51,7 @@
var/report_delivery = TRUE /// true if bot will announce an arrival to a location.
var/obj/item/stock_parts/cell/cell /// Internal Powercell
+ var/cell_move_power_usage = 1///How much power we use when we move.
var/bloodiness = 0 ///If we've run over a mob, how many tiles will we leave tracks on while moving
var/num_steps = 0 ///The amount of steps we should take until we rest for a time.
@@ -75,6 +77,7 @@
D.set_vehicle_dir_layer(NORTH, layer)
D.set_vehicle_dir_layer(EAST, layer)
D.set_vehicle_dir_layer(WEST, layer)
+ diag_hud_set_mulebotcell()
/mob/living/simple_animal/bot/mulebot/ComponentInitialize()
. = ..()
@@ -86,6 +89,7 @@
if(A == cell)
turn_off()
cell = null
+ diag_hud_set_mulebotcell()
return ..()
/mob/living/simple_animal/bot/mulebot/examine(mob/user)
@@ -148,6 +152,7 @@
if(!user.transferItemToLoc(I, src))
return
cell = I
+ diag_hud_set_mulebotcell()
visible_message("[user] inserts \a [cell] into [src].",
"You insert [cell] into [src].")
else if(I.tool_behaviour == TOOL_CROWBAR && open && user.a_intent != INTENT_HARM)
@@ -162,6 +167,7 @@
visible_message("[user] crowbars [cell] out from [src].",
"You pry [cell] out of [src].")
cell = null
+ diag_hud_set_mulebotcell()
else if(is_wire_tool(I) && open)
return attack_hand(user)
else if(load && ismob(load)) // chance to knock off rider
@@ -246,7 +252,7 @@
data["modeStatus"] = "average"
if(BOT_NO_ROUTE)
data["modeStatus"] = "bad"
- data["load"] = load ? load.name : null //IF YOU CHANGE THE NAME OF THIS, UPDATE MULEBOT/PARANORMAL/UI_DATA.
+ data["load"] = get_load_name()
data["destination"] = destination ? destination : null
data["home"] = home_destination
data["destinations"] = GLOB.deliverybeacontags
@@ -359,8 +365,9 @@
dat += "[mode_name[BOT_NO_ROUTE]]"
dat += "
"
- dat += "Current Load: [isobserver(load) ? "Unknown" : (load ? load.name : "None")] "
- dat += "Destination: [!destination ? "none" : destination] "
+ var/load_message = get_load_name()
+ dat += "Current Load:[load_message ? load_message : "None"] "
+ dat += "Destination: [!destination ? "None" : destination] "
dat += "Power level: [cell ? cell.percent() : 0]%"
if(locked && !ai && !isAdminGhostAI(user))
@@ -452,6 +459,10 @@
mode = BOT_IDLE
update_icon()
+///resolves the name to display for the loaded mob. primarily needed for the paranormal subtype since we don't want to show the name of ghosts riding it.
+/mob/living/simple_animal/bot/mulebot/proc/get_load_name()
+ return load ? load.name : null
+
/mob/living/simple_animal/bot/mulebot/proc/load_mob(mob/living/M)
can_buckle = TRUE
if(buckle_mob(M))
@@ -492,6 +503,15 @@
update_icon()
+/mob/living/simple_animal/bot/mulebot/Stat()
+ ..()
+ if(statpanel("Status"))
+ if(cell)
+ stat("Charge Left:", "[cell.charge]/[cell.maxcharge]")
+ else
+ stat(null, text("No Cell Inserted!"))
+ if(load)
+ stat("Current Load:", get_load_name())
/mob/living/simple_animal/bot/mulebot/call_bot()
..()
@@ -502,6 +522,9 @@
start()
/mob/living/simple_animal/bot/mulebot/Move(atom/newloc, direct) //handle leaving bloody tracks. can't be done via Moved() since that can end up putting the tracks somewhere BEFORE we get bloody.
+ if(!has_power((client || paicard))) //turn off if we ran out of power.
+ turn_off()
+ return FALSE
if(!bloodiness) //important to check this first since Bump() is called in the Move() -> Entered() chain
return ..()
var/atom/oldLoc = loc
@@ -513,6 +536,15 @@
B.setDir(direct)
bloodiness--
+/mob/living/simple_animal/bot/mulebot/Moved() //make sure we always use power after moving.
+ . = ..()
+ if(!cell)
+ return
+ cell.use(cell_move_power_usage)
+ if(cell.charge < cell_move_power_usage) //make sure we have enough power to move again, otherwise turn off.
+ turn_off()
+ diag_hud_set_mulebotcell()
+
/mob/living/simple_animal/bot/mulebot/handle_automated_action()
if(!on)
return
@@ -551,7 +583,6 @@
if(isturf(next))
var/oldloc = loc
var/moved = step_towards(src, next) // attempt to move
- cell.use(1)
if(moved && oldloc!=loc) // successful move
blockcount = 0
path -= loc
@@ -853,11 +884,10 @@
ghost_overlay.pixel_y = 12
. += ghost_overlay
-/mob/living/simple_animal/bot/mulebot/paranormal/ui_data(mob/user)
- var/list/data = ..()
- if(isobserver(load))
- data["load"] = "Unknown" //don't reveal the name of the ghost to prevent metagaming.
- return data
+/mob/living/simple_animal/bot/mulebot/paranormal/get_load_name() //Don't reveal the name of ghosts so we can't metagame who died and all that.
+ . = ..()
+ if(. && isobserver(load))
+ return "Unknown"
/mob/living/simple_animal/bot/mulebot/paranormal/proc/ghostmoved()
visible_message("The ghostly figure vanishes...")
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index 557c3135938..5975700e3a6 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -325,7 +325,7 @@ Auto Patrol: []"},
stun_attack(target)
mode = BOT_PREP_ARREST
- anchored = TRUE
+ set_anchored(TRUE)
target_lastloc = target.loc
return
@@ -359,7 +359,7 @@ Auto Patrol: []"},
if(BOT_ARREST)
if(!target)
- anchored = FALSE
+ set_anchored(FALSE)
mode = BOT_IDLE
last_found = world.time
frustration = 0
@@ -377,7 +377,7 @@ Auto Patrol: []"},
return
else //Try arresting again if the target escapes.
mode = BOT_PREP_ARREST
- anchored = FALSE
+ set_anchored(FALSE)
if(BOT_START_PATROL)
look_for_perp()
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
index a0bbb585dd7..f8124dc3078 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -535,20 +535,20 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the [mob_name] of [user.real_name]?", ROLE_PAI, null, FALSE, 100, POLL_IGNORE_HOLOPARASITE)
if(LAZYLEN(candidates))
- var/mob/dead/observer/C = pick(candidates)
- spawn_guardian(user, C.key)
+ var/mob/dead/observer/candidate = pick(candidates)
+ spawn_guardian(user, candidate)
else
to_chat(user, "[failure_message]")
used = FALSE
-/obj/item/guardiancreator/proc/spawn_guardian(mob/living/user, key)
+/obj/item/guardiancreator/proc/spawn_guardian(mob/living/user, mob/dead/candidate)
var/guardiantype = "Standard"
if(random)
guardiantype = pick(possible_guardians)
else
guardiantype = input(user, "Pick the type of [mob_name]", "[mob_name] Creation") as null|anything in sortList(possible_guardians)
- if(!guardiantype)
+ if(!guardiantype || !candidate.client)
to_chat(user, "[failure_message]" )
used = FALSE
return
@@ -596,7 +596,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
var/mob/living/simple_animal/hostile/guardian/G = new pickedtype(user, theme)
G.name = mob_name
G.summoner = user
- G.key = key
+ G.key = candidate.key
G.mind.enslave_mind_to_creator(user)
log_game("[key_name(user)] has summoned [key_name(G)], a [guardiantype] holoparasite.")
switch(theme)
diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm
index 1030ba9fd09..b9a4407a57e 100644
--- a/code/modules/mob/living/simple_animal/hostile/alien.dm
+++ b/code/modules/mob/living/simple_animal/hostile/alien.dm
@@ -177,11 +177,9 @@
/mob/living/simple_animal/hostile/alien/maid/AttackingTarget()
if(ismovable(target))
+ target.wash(CLEAN_WASH)
if(istype(target, /obj/effect/decal/cleanable))
visible_message("[src] cleans up \the [target].")
- qdel(target)
- return TRUE
- var/atom/movable/M = target
- SEND_SIGNAL(M, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
- visible_message("[src] polishes \the [target].")
+ else
+ visible_message("[src] polishes \the [target].")
return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm
index 7f3056ec998..056a448f349 100644
--- a/code/modules/mob/living/simple_animal/hostile/bear.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bear.dm
@@ -29,7 +29,7 @@
melee_damage_upper = 15
wound_bonus = -5
bare_wound_bonus = 10 // BEAR wound bonus am i right
- sharpness = TRUE
+ sharpness = SHARP_EDGED
attack_verb_continuous = "claws"
attack_verb_simple = "claw"
attack_sound = 'sound/weapons/bladeslice.ogg'
@@ -128,7 +128,7 @@
to_chat(user, "You strap the armor plating to [A] and sharpen [A.p_their()] claws with the nail filer. This was a great idea.")
qdel(src)
-mob/living/simple_animal/hostile/bear/butter //The mighty companion to Cak. Several functions used from it.
+/mob/living/simple_animal/hostile/bear/butter //The mighty companion to Cak. Several functions used from it.
name = "Terrygold"
icon_state = "butterbear"
icon_living = "butterbear"
@@ -173,7 +173,7 @@ mob/living/simple_animal/hostile/bear/butter //The mighty companion to Cak. Seve
to_chat(src, "Your name is now \"new_name\"!")
name = new_name
-mob/living/simple_animal/hostile/bear/butter/AttackingTarget() //Makes some attacks by the butter bear slip those who dare cross its path.
+/mob/living/simple_animal/hostile/bear/butter/AttackingTarget() //Makes some attacks by the butter bear slip those who dare cross its path.
if(isliving(target))
var/mob/living/L = target
if((L.mobility_flags & MOBILITY_STAND))
diff --git a/code/modules/mob/living/simple_animal/hostile/cockroach.dm b/code/modules/mob/living/simple_animal/hostile/cockroach.dm
index 8008858a8bd..f50d8fd9724 100644
--- a/code/modules/mob/living/simple_animal/hostile/cockroach.dm
+++ b/code/modules/mob/living/simple_animal/hostile/cockroach.dm
@@ -96,7 +96,7 @@
gold_core_spawnable = HOSTILE_SPAWN
attack_sound = 'sound/weapons/bladeslice.ogg'
faction = list("hostile")
- sharpness = IS_SHARP
+ sharpness = SHARP_POINTY
squish_chance = 0 // manual squish if relevant
/mob/living/simple_animal/hostile/cockroach/hauberoach/ComponentInitialize()
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 78fed859431..fa33a58b4a5 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -466,7 +466,7 @@
DestroyObjectsInDirection(direction)
-mob/living/simple_animal/hostile/proc/DestroySurroundings() // for use with megafauna destroying everything around them
+/mob/living/simple_animal/hostile/proc/DestroySurroundings() // for use with megafauna destroying everything around them
if(environment_smash)
EscapeConfinement()
for(var/dir in GLOB.cardinals)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index f469e1edb5c..d18d05fda0c 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -291,6 +291,7 @@ Difficulty: Very Hard
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
pixel_y = -4
use_power = NO_POWER_USE
+ base_build_path = /obj/machinery/smartfridge/black_box
var/memory_saved = FALSE
var/list/stored_items = list()
var/list/blacklist = list()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
index 01b718a4b3b..d9f3340e3f6 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
@@ -395,7 +395,7 @@ Difficulty: Medium
return FALSE
return ..()
-/mob/living/simple_animal/hostile/megafauna/dragon/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs)
+/mob/living/simple_animal/hostile/megafauna/dragon/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE)
if(swooping & SWOOP_INVULNERABLE) //to suppress attack messages without overriding every single proc that could send a message saying we got hit
return
return ..()
@@ -520,7 +520,7 @@ Difficulty: Medium
else
animate(src, pixel_x = -16, pixel_z = 0, time = 5)
-obj/effect/temp_visual/fireball
+/obj/effect/temp_visual/fireball
icon = 'icons/obj/wizard.dmi'
icon_state = "fireball"
name = "fireball"
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
index 7d0a40810dd..2d5542c6887 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -519,15 +519,15 @@ Difficulty: Hard
icon_state = "wall"
light_range = MINIMUM_USEFUL_LIGHT_RANGE
duration = 100
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_TRUE
/obj/effect/temp_visual/hierophant/wall/Initialize(mapload, new_caster)
. = ..()
- queue_smooth_neighbors(src)
- queue_smooth(src)
+ QUEUE_SMOOTH_NEIGHBORS(src)
+ QUEUE_SMOOTH(src)
/obj/effect/temp_visual/hierophant/wall/Destroy()
- queue_smooth_neighbors(src)
+ QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
/obj/effect/temp_visual/hierophant/wall/CanAllowThrough(atom/movable/mover, turf/target)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
index de280a579d1..c0c9e3018d8 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
@@ -102,7 +102,7 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa
/mob/living/simple_animal/hostile/swarmer/ai/Initialize()
. = ..()
- ToggleLight() //so you can see them eating you out of house and home/shooting you/stunlocking you for eternity
+ toggle_light() //so you can see them eating you out of house and home/shooting you/stunlocking you for eternity
LAZYINITLIST(GLOB.AISwarmersByType[type])
GLOB.AISwarmers += src
GLOB.AISwarmersByType[type] += src
@@ -114,7 +114,7 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa
return ..()
-/mob/living/simple_animal/hostile/swarmer/ai/SwarmerTypeToCreate()
+/mob/living/simple_animal/hostile/swarmer/ai/swarmer_type_to_create()
return GetUncappedAISwarmerType()
@@ -124,7 +124,7 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa
if(!stop_automated_movement)
if(health < maxHealth*0.25)
StartAction(100)
- RepairSelf()
+ repair_self()
return
@@ -211,16 +211,16 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa
if(!stop_automated_movement)
if(GLOB.AISwarmers.len < GetTotalAISwarmerCap() && resources >= 50)
StartAction(100) //so they'll actually sit still and use the verbs
- CreateSwarmer()
+ create_swarmer()
return
if(resources > 5)
if(prob(5)) //lower odds, as to prioritise reproduction
StartAction(10) //not a typo
- CreateBarricade()
+ create_barricade()
return
if(prob(5))
- CreateTrap()
+ create_trap()
return
@@ -267,7 +267,7 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa
if(isliving(target))
if(prob(35))
StartAction(30)
- DisperseTarget(target)
+ prepare_target(target)
else
var/mob/living/L = target
L.attack_animal(src)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
index ef5784a93a0..fd4b832575d 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
@@ -81,7 +81,7 @@
projectiletype = /obj/projectile/temp/basilisk/heated
addtimer(CALLBACK(src, .proc/cool_down), 3000)
-mob/living/simple_animal/hostile/asteroid/basilisk/proc/cool_down()
+/mob/living/simple_animal/hostile/asteroid/basilisk/proc/cool_down()
visible_message("[src] appears to be cooling down...")
if(stat != DEAD)
icon_state = "Basilisk"
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
index 16a1480e8ec..a5249905cdd 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
@@ -304,7 +304,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
icon = 'icons/turf/walls/hierophant_wall_temp.dmi'
icon_state = "wall"
duration = 50
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_TRUE
layer = BELOW_MOB_LAYER
color = rgb(255,0,0)
light_range = MINIMUM_USEFUL_LIGHT_RANGE
@@ -314,11 +314,11 @@ While using this makes the system rely on OnFire, it still gives options for tim
/obj/effect/temp_visual/elite_tumor_wall/Initialize(mapload, new_caster)
. = ..()
- queue_smooth_neighbors(src)
- queue_smooth(src)
+ QUEUE_SMOOTH_NEIGHBORS(src)
+ QUEUE_SMOOTH(src)
/obj/effect/temp_visual/elite_tumor_wall/Destroy()
- queue_smooth_neighbors(src)
+ QUEUE_SMOOTH_NEIGHBORS(src)
activator = null
ourelite = null
return ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
index b0793ee453e..dec465d9516 100644
--- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
+++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
@@ -209,7 +209,7 @@
* Arguments:
* * turf/T - The turf to trigger the effects on.
*/
-mob/living/simple_animal/hostile/space_dragon/proc/dragon_fire_line(turf/T)
+/mob/living/simple_animal/hostile/space_dragon/proc/dragon_fire_line(turf/T)
var/list/hit_list = list()
hit_list += src
new /obj/effect/hotspot(T)
diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm
index 32039a58efb..936a98ab718 100644
--- a/code/modules/mob/living/simple_animal/hostile/statue.dm
+++ b/code/modules/mob/living/simple_animal/hostile/statue.dm
@@ -108,7 +108,7 @@
if(!can_be_seen(get_turf(loc)))
..()
-/mob/living/simple_animals/hostile/statue/IsVocal() //we're a statue, of course we can't talk.
+/mob/living/simple_animal/hostile/statue/IsVocal() //we're a statue, of course we can't talk.
return FALSE
/mob/living/simple_animal/hostile/statue/proc/can_be_seen(turf/destination)
diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm
index 4804bb09e59..3deed6857b1 100644
--- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm
+++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm
@@ -296,7 +296,7 @@
melee_damage_upper = 15
wound_bonus = -10
bare_wound_bonus = 20
- sharpness = TRUE
+ sharpness = SHARP_EDGED
obj_damage = 0
environment_smash = ENVIRONMENT_SMASH_NONE
attack_verb_continuous = "cuts"
diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
index c07032fe4e3..37af7f4139c 100644
--- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
+++ b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
@@ -16,7 +16,7 @@
layer = SPACEVINE_MOB_LAYER
opacity = 0
canSmoothWith = list()
- smooth = SMOOTH_FALSE
+ smoothing_flags = NONE
/// The amount of time it takes to create a venus human trap, in deciseconds
var/growth_time = 1200
@@ -37,7 +37,7 @@
* Spawns a venus human trap, then qdels itself.
*
* Displays a message, spawns a human venus trap, then qdels itself.
- */
+ */
/obj/structure/alien/resin/flower_bud_enemy/proc/bear_fruit()
visible_message("The plant has borne fruit!")
new /mob/living/simple_animal/hostile/venus_human_trap(get_turf(src))
@@ -103,7 +103,7 @@
/mob/living/simple_animal/hostile/venus_human_trap/Life()
. = ..()
pull_vines()
-
+
/mob/living/simple_animal/hostile/venus_human_trap/AttackingTarget()
. = ..()
if(isliving(target))
@@ -125,7 +125,7 @@
for(var/obj/O in T)
if(O.density)
return
-
+
var/datum/beam/newVine = Beam(the_target, "vine", time=INFINITY, maxdistance = vine_grab_distance, beam_type=/obj/effect/ebeam/vine)
RegisterSignal(newVine, COMSIG_PARENT_QDELETING, .proc/remove_vine, newVine)
vines += newVine
@@ -188,5 +188,5 @@
* Arguments:
* * datum/beam/vine - The vine to be removed from the list.
*/
-mob/living/simple_animal/hostile/venus_human_trap/proc/remove_vine(datum/beam/vine, force)
+/mob/living/simple_animal/hostile/venus_human_trap/proc/remove_vine(datum/beam/vine, force)
vines -= vine
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 4f3f481226a..a7625a7b6ef 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -150,7 +150,7 @@
///How much bare wounding power it has
var/bare_wound_bonus = 0
///If the attacks from this are sharp
- var/sharpness = FALSE
+ var/sharpness = SHARP_NONE
///Generic flags
var/simple_mob_flags = NONE
diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm
index fec024cebf2..014929f6eee 100644
--- a/code/modules/mob/living/taste.dm
+++ b/code/modules/mob/living/taste.dm
@@ -18,7 +18,7 @@
/mob/living/proc/taste(datum/reagents/from)
if(last_taste_time + 50 < world.time)
var/taste_sensitivity = get_taste_sensitivity()
- var/text_output = from.generate_taste_message(taste_sensitivity)
+ var/text_output = from.generate_taste_message(taste_sensitivity,src)
// We dont want to spam the same message over and over again at the
// person. Give it a bit of a buffer.
if(hallucination > 50 && prob(25))
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 66a75d6f54b..e1a27d230c9 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -179,19 +179,27 @@
* * vision_distance (optional) define how many tiles away the message can be seen.
* * ignored_mob (optional) doesn't show any message to a given mob if TRUE.
*/
-/atom/proc/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs)
+/atom/proc/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE)
var/turf/T = get_turf(src)
if(!T)
return
+
if(!islist(ignored_mobs))
ignored_mobs = list(ignored_mobs)
var/list/hearers = get_hearers_in_view(vision_distance, src) //caches the hearers and then removes ignored mobs.
hearers -= ignored_mobs
+
if(self_message)
hearers -= src
+
+ var/raw_msg = message
+ if(visible_message_flags & EMOTE_MESSAGE)
+ message = "[src] [message]"
+
for(var/mob/M in hearers)
if(!M.client)
continue
+
//This entire if/else chain could be in two lines but isn't for readibilties sake.
var/msg = message
if(M.see_invisible < invisibility)//if src is invisible to M
@@ -202,10 +210,15 @@
msg = blind_message
if(!msg)
continue
+
+ if(visible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(M, visible_message_flags))
+ M.create_chat_message(src, raw_message = raw_msg, runechat_flags = visible_message_flags)
+
M.show_message(msg, MSG_VISUAL, blind_message, MSG_AUDIBLE)
+
///Adds the functionality to self_message.
-/mob/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs)
+/mob/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE)
. = ..()
if(self_message)
show_message(self_message, MSG_VISUAL, blind_message, MSG_AUDIBLE)
@@ -220,11 +233,16 @@
* * deaf_message (optional) is what deaf people will see.
* * hearing_distance (optional) is the range, how many tiles away the message can be heard.
*/
-/atom/proc/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message)
+/atom/proc/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message, audible_message_flags = NONE)
var/list/hearers = get_hearers_in_view(hearing_distance, src)
if(self_message)
hearers -= src
+ var/raw_msg = message
+ if(audible_message_flags & EMOTE_MESSAGE)
+ message = "[src] [message]"
for(var/mob/M in hearers)
+ if(audible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(M, audible_message_flags))
+ M.create_chat_message(src, raw_message = raw_msg, runechat_flags = audible_message_flags)
M.show_message(message, MSG_AUDIBLE, deaf_message, MSG_VISUAL)
/**
@@ -238,11 +256,28 @@
* * deaf_message (optional) is what deaf people will see.
* * hearing_distance (optional) is the range, how many tiles away the message can be heard.
*/
-/mob/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message)
+/mob/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message, audible_message_flags = NONE)
. = ..()
if(self_message)
show_message(self_message, MSG_AUDIBLE, deaf_message, MSG_VISUAL)
+
+///Returns the client runechat visible messages preference according to the message type.
+/atom/proc/runechat_prefs_check(mob/target, visible_message_flags = NONE)
+ if(!target.client?.prefs.chat_on_map || !target.client.prefs.see_chat_non_mob)
+ return FALSE
+ if(visible_message_flags & EMOTE_MESSAGE && !target.client.prefs.see_rc_emotes)
+ return FALSE
+ return TRUE
+
+/mob/runechat_prefs_check(mob/target, visible_message_flags = NONE)
+ if(!target.client?.prefs.chat_on_map)
+ return FALSE
+ if(visible_message_flags & EMOTE_MESSAGE && !target.client.prefs.see_rc_emotes)
+ return FALSE
+ return TRUE
+
+
///Get the item on the mob in the storage slot identified by the id passed in
/mob/proc/get_item_by_slot(slot_id)
return null
@@ -252,7 +287,7 @@
return
///Is the mob incapacitated
-/mob/proc/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, check_immobilized = FALSE)
+/mob/proc/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, ignore_stasis = FALSE)
return
/**
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index e07f93fef57..d6b6e4839df 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -205,8 +205,6 @@
var/datum/h_sandbox/sandbox = null
- var/bloody_hands = 0
-
var/datum/focus //What receives our keyboard inputs. src by default
/// Used for tracking last uses of emotes for cooldown purposes
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 4fbb4bf4669..71bd9ae285c 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -297,52 +297,18 @@
// moved out of admins.dm because things other than admin procs were calling this.
-/**
- * Is this mob special to the gamemode?
- *
- * returns 1 for special characters and 2 for heroes of gamemode
- *
- */
+/// Returns TRUE if the game has started and we're either an AI with a 0th law, or we're someone with a special role/antag datum
/proc/is_special_character(mob/M)
if(!SSticker.HasRoundStarted())
return FALSE
if(!istype(M))
return FALSE
- if(issilicon(M))
- if(iscyborg(M)) //For cyborgs, returns 1 if the cyborg has a law 0 and special_role. Returns 0 if the borg is merely slaved to an AI traitor.
- return FALSE
- else if(isAI(M))
- var/mob/living/silicon/ai/A = M
- if(A.laws && A.laws.zeroth && A.mind && A.mind.special_role)
- return TRUE
+ if(iscyborg(M)) //as a borg you're now beholden to your laws rather than greentext
return FALSE
- if(M.mind && M.mind.special_role)//If they have a mind and special role, they are some type of traitor or antagonist.
- switch(SSticker.mode.config_tag)
- if("revolution")
- if(is_revolutionary(M))
- return 2
- if("cult")
- if(M.mind in SSticker.mode.cult)
- return 2
- if("nuclear")
- if(M.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE))
- return 2
- if("changeling")
- if(M.mind.has_antag_datum(/datum/antagonist/changeling,TRUE))
- return 2
- if("wizard")
- if(iswizard(M))
- return 2
- if("apprentice")
- if(M.mind in SSticker.mode.apprentices)
- return 2
- if("monkey")
- if(isliving(M))
- var/mob/living/L = M
- if(L.diseases && (locate(/datum/disease/transformation/jungle_fever) in L.diseases))
- return 2
- return TRUE
- if(M.mind && LAZYLEN(M.mind.antag_datums)) //they have an antag datum!
+ if(isAI(M))
+ var/mob/living/silicon/ai/A = M
+ return (A.laws?.zeroth && (A.mind?.special_role || !isnull(M.mind?.antag_datums)))
+ if(M.mind?.special_role || !isnull(M.mind?.antag_datums)) //they have an antag datum!
return TRUE
return FALSE
diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm
index 8b57f5f8fa0..a21c92a399c 100644
--- a/code/modules/mob/mob_say.dm
+++ b/code/modules/mob/mob_say.dm
@@ -116,6 +116,10 @@
*/
/mob/proc/get_message_mods(message, list/mods)
for(var/I in 1 to MESSAGE_MODS_LENGTH)
+ // Prevents "...text" from being read as a radio message
+ if (length(message) > 1 && message[2] == message[1])
+ continue
+
var/key = message[1]
var/chop_to = 2 //By default we just take off the first char
if(key == "#" && !mods[WHISPER_MODE])
diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm
index cdd00377b97..fd017e2b0f8 100644
--- a/code/modules/modular_computers/computers/item/computer_ui.dm
+++ b/code/modules/modular_computers/computers/item/computer_ui.dm
@@ -36,9 +36,9 @@
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, "NtosMain")
- ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
ui.set_autoupdate(TRUE)
ui.open()
+ ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
/obj/item/modular_computer/ui_data(mob/user)
diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm
index bf6c6a42730..cb593ff7d01 100644
--- a/code/modules/modular_computers/file_system/program.dm
+++ b/code/modules/modular_computers/file_system/program.dm
@@ -170,8 +170,8 @@
ui = SStgui.try_update_ui(user, src, ui)
if(!ui && tgui_id)
ui = new(user, src, tgui_id, filedesc)
- ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
ui.open()
+ ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
// CONVENTIONS, READ THIS WHEN CREATING NEW PROGRAM AND OVERRIDING THIS PROC:
// Topic calls are automagically forwarded from NanoModule this program contains.
diff --git a/code/modules/modular_computers/file_system/programs/airestorer.dm b/code/modules/modular_computers/file_system/programs/airestorer.dm
index c2a1aa4ca75..b86d2785277 100644
--- a/code/modules/modular_computers/file_system/programs/airestorer.dm
+++ b/code/modules/modular_computers/file_system/programs/airestorer.dm
@@ -71,14 +71,18 @@
ai_slot.locked = FALSE
restoring = FALSE
return
- ai_slot.locked =TRUE
- A.adjustOxyLoss(-5, 0)
- A.adjustFireLoss(-5, 0)
- A.adjustToxLoss(-5, 0)
- A.adjustBruteLoss(-5, 0)
+ ai_slot.locked = TRUE
+ A.adjustOxyLoss(-5, FALSE)
+ A.adjustFireLoss(-5, FALSE)
+ A.adjustBruteLoss(-5, FALSE)
+
+ // Please don't forget to update health, otherwise the below if statements will probably always fail.
A.updatehealth()
+
if(A.health >= 0 && A.stat == DEAD)
A.revive(full_heal = FALSE, admin_revive = FALSE)
+ cardhold.update_icon()
+
// Finished restoring
if(A.health >= 100)
ai_slot.locked = FALSE
diff --git a/code/modules/ninja/energy_katana.dm b/code/modules/ninja/energy_katana.dm
index 151952097fe..e21894eb0cc 100644
--- a/code/modules/ninja/energy_katana.dm
+++ b/code/modules/ninja/energy_katana.dm
@@ -14,7 +14,7 @@
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut")
slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
max_integrity = 200
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
var/datum/effect_system/spark_spread/spark_system
diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm
index ccce6c3af2b..a0c6073281a 100644
--- a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm
+++ b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm
@@ -14,5 +14,5 @@
/obj/item/throwing_star/ninja
name = "ninja throwing star"
- throwforce = 30
+ throwforce = 20
embedding = list("pain_mult" = 6, "embed_chance" = 100, "fall_chance" = 0)
diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm
index 68b385abf76..437d8f676bb 100644
--- a/code/modules/paperwork/clipboard.dm
+++ b/code/modules/paperwork/clipboard.dm
@@ -3,6 +3,7 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "clipboard"
inhand_icon_state = "clipboard"
+ worn_icon_state = "clipboard"
throwforce = 0
w_class = WEIGHT_CLASS_SMALL
throw_speed = 3
diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm
index e4a9f8168dc..2ce129daa0d 100644
--- a/code/modules/paperwork/filingcabinet.dm
+++ b/code/modules/paperwork/filingcabinet.dm
@@ -50,7 +50,7 @@
to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].")
if(P.use_tool(src, user, 20, volume=50))
to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].")
- anchored = !anchored
+ set_anchored(!anchored)
else if(P.w_class < WEIGHT_CLASS_NORMAL)
if(!user.transferItemToLoc(P, src))
return
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index ac3a4d8ff4f..1d21d88dd08 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -58,6 +58,7 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "paper"
inhand_icon_state = "paper"
+ worn_icon_state = "paper"
custom_fire_overlay = "paper_onfire_overlay"
throwforce = 0
w_class = WEIGHT_CLASS_TINY
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 013a025391b..898c133a8a9 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -29,6 +29,7 @@
var/degrees = 0
var/font = PEN_FONT
embedding = list()
+ sharpness = SHARP_POINTY
/obj/item/pen/suicide_act(mob/user)
user.visible_message("[user] is scribbling numbers all over [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit sudoku...")
@@ -101,7 +102,7 @@
throw_speed = 4
colour = "crimson"
custom_materials = list(/datum/material/gold = 750)
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
resistance_flags = FIRE_PROOF
unique_reskin = list("Oak" = "pen-fountain-o",
"Gold" = "pen-fountain-g",
@@ -196,7 +197,7 @@
*/
/obj/item/pen/edagger
attack_verb = list("slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut") //these won't show up if the pen is off
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
var/on = FALSE
/obj/item/pen/edagger/ComponentInitialize()
diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm
index 9a375f09724..2ca575b5d7a 100644
--- a/code/modules/paperwork/photocopier.dm
+++ b/code/modules/paperwork/photocopier.dm
@@ -29,6 +29,10 @@
var/mob/living/ass //i can't believe i didn't write a stupid-ass comment about this var when i first coded asscopy.
var/busy = FALSE
+/obj/machinery/photocopier/Initialize()
+ . = ..()
+ AddComponent(/datum/component/payment, 5, SSeconomy.get_dep_account(ACCOUNT_CIV), PAYMENT_CLINICAL)
+
/obj/machinery/photocopier/ui_interact(mob/user)
. = ..()
var/list/dat = list("Photocopier
")
@@ -56,8 +60,12 @@
return
if(href_list["copy"])
if(copy)
+ if(busy)
+ return
for(var/i = 0, i < copies, i++)
- if(toner > 0 && !busy && copy)
+ if(toner > 0 && copy)
+ if(attempt_charge(src, usr) & COMPONENT_OBJ_CANCEL_CHARGE)
+ return
var/copy_as_paper = 1
if(istype(copy, /obj/item/paper/contract/employment))
var/obj/item/paper/contract/employment/E = copy
@@ -89,16 +97,24 @@
break
updateUsrDialog()
else if(photocopy)
+ if(busy)
+ return
for(var/i = 0, i < copies, i++)
- if(toner >= 5 && !busy && photocopy) //Was set to = 0, but if there was say 3 toner left and this ran, you would get -2 which would be weird for ink
+ if(attempt_charge(src, usr) & COMPONENT_OBJ_CANCEL_CHARGE)
+ return
+ if(toner >= 5 && photocopy) //Was set to = 0, but if there was say 3 toner left and this ran, you would get -2 which would be weird for ink
new /obj/item/photo (loc, photocopy.picture.Copy(greytoggle == "Greyscale"? TRUE : FALSE))
busy = TRUE
addtimer(CALLBACK(src, .proc/reset_busy), 1.5 SECONDS)
else
break
else if(doccopy)
+ if(busy)
+ return
for(var/i = 0, i < copies, i++)
- if(toner > 5 && !busy && doccopy)
+ if(attempt_charge(src, usr) & COMPONENT_OBJ_CANCEL_CHARGE)
+ return
+ if(toner > 5 && doccopy)
new /obj/item/documents/photocopy(loc, doccopy)
toner-= 6 // the sprite shows 6 papers, yes I checked
busy = TRUE
@@ -107,7 +123,11 @@
break
updateUsrDialog()
else if(ass) //ASS COPY. By Miauw
+ if(busy)
+ return
for(var/i = 0, i < copies, i++)
+ if(attempt_charge(src, usr) & COMPONENT_OBJ_CANCEL_CHARGE)
+ return
var/icon/temp_img
if(ishuman(ass) && (ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) || ass.get_item_by_slot(ITEM_SLOT_OCLOTHING)))
to_chat(usr, "You feel kind of silly, copying [ass == usr ? "your" : ass][ass == usr ? "" : "\'s"] ass with [ass == usr ? "your" : "[ass.p_their()]"] clothes on." )
diff --git a/code/modules/photography/photos/album.dm b/code/modules/photography/photos/album.dm
index 4d12d0c8565..b09805c3cb6 100644
--- a/code/modules/photography/photos/album.dm
+++ b/code/modules/photography/photos/album.dm
@@ -96,10 +96,10 @@
name = "photo album (Library)"
persistence_id = "library"
-obj/item/storage/photo_album/chapel
+/obj/item/storage/photo_album/chapel
name = "photo album (Chapel)"
persistence_id = "chapel"
-obj/item/storage/photo_album/prison
+/obj/item/storage/photo_album/prison
name = "photo album (Prison)"
persistence_id = "prison"
diff --git a/code/modules/plumbing/ducts.dm b/code/modules/plumbing/ducts.dm
index d57dec29d5b..cb94f281dba 100644
--- a/code/modules/plumbing/ducts.dm
+++ b/code/modules/plumbing/ducts.dm
@@ -41,7 +41,7 @@ All the important duct code:
if(no_anchor)
active = FALSE
- anchored = FALSE
+ set_anchored(FALSE)
else if(!can_anchor())
qdel(src)
CRASH("Overlapping ducts detected")
@@ -152,8 +152,9 @@ All the important duct code:
return TRUE
///we disconnect ourself from our neighbours. we also destroy our ductnet and tell our neighbours to make a new one
-/obj/machinery/duct/proc/disconnect_duct()
- anchored = FALSE
+/obj/machinery/duct/proc/disconnect_duct(skipanchor)
+ if(!skipanchor) //since set_anchored calls us too.
+ set_anchored(FALSE)
active = FALSE
if(duct)
duct.remove_duct(src)
@@ -272,24 +273,26 @@ All the important duct code:
pixel_y = offset
+/obj/machinery/duct/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return
+ if(anchorvalue)
+ active = TRUE
+ attempt_connect()
+ else
+ disconnect_duct(TRUE)
+
/obj/machinery/duct/wrench_act(mob/living/user, obj/item/I) //I can also be the RPD
..()
add_fingerprint(user)
I.play_tool_sound(src)
- if(anchored)
+ if(anchored || can_anchor())
+ set_anchored(!anchored)
user.visible_message( \
- "[user] unfastens \the [src].", \
- "You unfasten \the [src].", \
+ "[user] [anchored ? null : "un"]fastens \the [src].", \
+ "You [anchored ? null : "un"]fasten \the [src].", \
"You hear ratcheting.")
- disconnect_duct()
- else if(can_anchor())
- anchored = TRUE
- active = TRUE
- user.visible_message( \
- "[user] fastens \the [src].", \
- "You fasten \the [src].", \
- "You hear ratcheting.")
- attempt_connect()
return TRUE
///collection of all the sanity checks to prevent us from stacking ducts that shouldn't be stacked
/obj/machinery/duct/proc/can_anchor(turf/T)
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index 8f96e0cf003..12c3e5fccae 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -379,7 +379,7 @@ GLOBAL_LIST_INIT(wire_node_generating_types, typecacheof(list(/obj/structure/gri
// Definitions
////////////////////////////////
-GLOBAL_LIST_INIT(cable_coil_recipes, list(new/datum/stack_recipe("cable restraints", /obj/item/restraints/handcuffs/cable, 15), new/datum/stack_recipe("multilayer cable", /obj/structure/cable/multilayer, 1), new/datum/stack_recipe("multiZ cable", /obj/structure/cable/multilayer/multiz, 1)))
+GLOBAL_LIST_INIT(cable_coil_recipes, list(new/datum/stack_recipe("cable restraints", /obj/item/restraints/handcuffs/cable, 15)))
/obj/item/stack/cable_coil
name = "cable coil"
diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm
index e5940e2ad84..5f723f72c6e 100644
--- a/code/modules/power/generator.dm
+++ b/code/modules/power/generator.dm
@@ -189,7 +189,7 @@
. = ..()
if(!panel_open)
return
- anchored = !anchored
+ set_anchored(!anchored)
I.play_tool_sound(src)
if(!anchored)
kill_circs()
diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm
index 47ca16f3a2a..636ad2b3488 100644
--- a/code/modules/power/port_gen.dm
+++ b/code/modules/power/port_gen.dm
@@ -172,6 +172,15 @@
/obj/machinery/power/port_gen/pacman/proc/overheat()
explosion(src.loc, 2, 5, 2, -1)
+/obj/machinery/power/port_gen/pacman/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return //no need to process if we didn't change anything.
+ if(anchorvalue)
+ connect_to_network()
+ else
+ disconnect_from_network()
+
/obj/machinery/power/port_gen/pacman/attackby(obj/item/O, mob/user, params)
if(istype(O, sheet_path))
var/obj/item/stack/addstack = O
@@ -186,12 +195,10 @@
else if(!active)
if(O.tool_behaviour == TOOL_WRENCH)
if(!anchored && !isinspace())
- anchored = TRUE
- connect_to_network()
+ set_anchored(TRUE)
to_chat(user, "You secure the generator to the floor.")
else if(anchored)
- anchored = FALSE
- disconnect_from_network()
+ set_anchored(FALSE)
to_chat(user, "You unsecure the generator from the floor.")
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm
index a2f6603ef47..369224af6dc 100644
--- a/code/modules/power/singularity/collector.dm
+++ b/code/modules/power/singularity/collector.dm
@@ -23,8 +23,9 @@
var/drainratio = 1
var/powerproduction_drain = 0.001
-/obj/machinery/power/rad_collector/anchored
- anchored = TRUE
+/obj/machinery/power/rad_collector/anchored/Initialize()
+ . = ..()
+ set_anchored(TRUE)
/obj/machinery/power/rad_collector/anchored/delta //Deltastation's engine is shared by engineers and atmos techs
desc = "A device which uses Hawking Radiation and plasma to produce power. This model allows access by Atmospheric Technicians."
@@ -77,13 +78,14 @@
return FAILED_UNFASTEN
return ..()
-/obj/machinery/power/rad_collector/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
+/obj/machinery/power/rad_collector/set_anchored(anchorvalue)
. = ..()
- if(. == SUCCESSFUL_UNFASTEN)
- if(anchored)
- connect_to_network()
- else
- disconnect_from_network()
+ if(isnull(.))
+ return //no need to process if we didn't change anything.
+ if(anchorvalue)
+ connect_to_network()
+ else
+ disconnect_from_network()
/obj/machinery/power/rad_collector/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/tank/internals/plasma))
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm
index 59e49a35ef7..3d0fe96f2a7 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/singularity/emitter.dm
@@ -60,7 +60,7 @@
wires = new /datum/wires/emitter(src)
if(welded)
if(!anchored)
- setAnchored(TRUE)
+ set_anchored(TRUE)
connect_to_network()
sparks = new
@@ -71,7 +71,7 @@
. = ..()
AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES)
-/obj/machinery/power/emitter/setAnchored(anchorvalue)
+/obj/machinery/power/emitter/set_anchored(anchorvalue)
. = ..()
if(!anchored && welded) //make sure they're keep in sync in case it was forcibly unanchored by badmins or by a megafauna.
welded = FALSE
@@ -168,7 +168,7 @@
/obj/machinery/power/emitter/attack_animal(mob/living/simple_animal/M)
if(ismegafauna(M) && anchored)
- setAnchored(FALSE)
+ set_anchored(FALSE)
M.visible_message("[M] rips [src] free from its moorings!")
else
. = ..()
diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm
index 45172ba50e0..4eb625c73ea 100644
--- a/code/modules/power/singularity/field_generator.dm
+++ b/code/modules/power/singularity/field_generator.dm
@@ -61,7 +61,7 @@ field_generator power level display
/obj/machinery/field/generator/anchored/Initialize()
. = ..()
- setAnchored(TRUE)
+ set_anchored(TRUE)
/obj/machinery/field/generator/ComponentInitialize()
. = ..()
@@ -88,8 +88,10 @@ field_generator power level display
else
to_chat(user, "[src] needs to be firmly secured to the floor first!")
-/obj/machinery/field/generator/setAnchored(anchorvalue)
+/obj/machinery/field/generator/set_anchored(anchorvalue)
. = ..()
+ if(isnull(.))
+ return
if(active)
turn_off()
state = anchorvalue ? FG_SECURED : FG_UNSECURED
@@ -147,7 +149,7 @@ field_generator power level display
/obj/machinery/field/generator/attack_animal(mob/living/simple_animal/M)
if(M.environment_smash & ENVIRONMENT_SMASH_RWALLS && active == FG_OFFLINE && state != FG_UNSECURED)
- setAnchored(FALSE)
+ set_anchored(FALSE)
M.visible_message("[M] rips [src] free from its moorings!")
else
..()
diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
index a37aa2a2ca3..816fa750795 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
@@ -59,6 +59,14 @@
AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS )
+/obj/structure/particle_accelerator/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return
+ construction_state = anchorvalue ? PA_CONSTRUCTION_UNWIRED : PA_CONSTRUCTION_UNSECURED
+ update_state()
+ update_icon()
+
/obj/structure/particle_accelerator/attackby(obj/item/W, mob/user, params)
var/did_something = FALSE
@@ -66,19 +74,19 @@
if(PA_CONSTRUCTION_UNSECURED)
if(W.tool_behaviour == TOOL_WRENCH && !isinspace())
W.play_tool_sound(src, 75)
- anchored = TRUE
+ set_anchored(TRUE)
user.visible_message("[user.name] secures the [name] to the floor.", \
"You secure the external bolts.")
- construction_state = PA_CONSTRUCTION_UNWIRED
- did_something = TRUE
+ user.changeNext_move(CLICK_CD_MELEE)
+ return //set_anchored handles the rest of the stuff we need to do.
if(PA_CONSTRUCTION_UNWIRED)
if(W.tool_behaviour == TOOL_WRENCH)
W.play_tool_sound(src, 75)
- anchored = FALSE
+ set_anchored(FALSE)
user.visible_message("[user.name] detaches the [name] from the floor.", \
"You remove the external bolts.")
- construction_state = PA_CONSTRUCTION_UNSECURED
- did_something = TRUE
+ user.changeNext_move(CLICK_CD_MELEE)
+ return //set_anchored handles the rest of the stuff we need to do.
else if(istype(W, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/CC = W
if(CC.use(1))
diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/singularity/particle_accelerator/particle_control.dm
index f26edda43fc..d5a3179731b 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_control.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm
@@ -199,6 +199,14 @@
if(PA_CONSTRUCTION_PANEL_OPEN)
. += "The panel is open."
+/obj/machinery/particle_accelerator/control_box/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return
+ construction_state = anchorvalue ? PA_CONSTRUCTION_UNWIRED : PA_CONSTRUCTION_UNSECURED
+ update_state()
+ update_icon()
+
/obj/machinery/particle_accelerator/control_box/attackby(obj/item/W, mob/user, params)
var/did_something = FALSE
@@ -206,19 +214,19 @@
if(PA_CONSTRUCTION_UNSECURED)
if(W.tool_behaviour == TOOL_WRENCH && !isinspace())
W.play_tool_sound(src, 75)
- anchored = TRUE
+ set_anchored(TRUE)
user.visible_message("[user.name] secures the [name] to the floor.", \
"You secure the external bolts.")
- construction_state = PA_CONSTRUCTION_UNWIRED
- did_something = TRUE
+ user.changeNext_move(CLICK_CD_MELEE)
+ return //set_anchored handles the rest of the stuff we need to do.
if(PA_CONSTRUCTION_UNWIRED)
if(W.tool_behaviour == TOOL_WRENCH)
W.play_tool_sound(src, 75)
- anchored = FALSE
+ set_anchored(FALSE)
user.visible_message("[user.name] detaches the [name] from the floor.", \
"You remove the external bolts.")
- construction_state = PA_CONSTRUCTION_UNSECURED
- did_something = TRUE
+ user.changeNext_move(CLICK_CD_MELEE)
+ return //set_anchored handles the rest of the stuff we need to do.
else if(istype(W, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/CC = W
if(CC.use(1))
@@ -250,7 +258,7 @@
update_icon()
return
- ..()
+ return ..()
/obj/machinery/particle_accelerator/control_box/blob_act(obj/structure/blob/B)
if(prob(50))
diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm
index c8bdb7831f6..fd9fb0e3078 100644
--- a/code/modules/power/solar.dm
+++ b/code/modules/power/solar.dm
@@ -59,7 +59,7 @@
if(!S)
S = new /obj/item/solar_assembly(src)
S.glass_type = /obj/item/stack/sheet/glass
- S.anchored = TRUE
+ S.set_anchored(TRUE)
else
S.forceMove(src)
if(S.glass_type == /obj/item/stack/sheet/rglass) //if the panel is in reinforced glass
@@ -229,23 +229,21 @@
new glass_type(Tsec, 2)
glass_type = null
+/obj/item/solar_assembly/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return
+ randomise_offset(anchored ? 0 : random_offset)
/obj/item/solar_assembly/attackby(obj/item/W, mob/user, params)
if(W.tool_behaviour == TOOL_WRENCH && isturf(loc))
if(isinspace())
to_chat(user, "You can't secure [src] here.")
return
- anchored = !anchored
- if(anchored)
- user.visible_message("[user] wrenches the solar assembly into place.", "You wrench the solar assembly into place.")
- W.play_tool_sound(src, 75)
- pixel_x = 0
- pixel_y = 0
- else
- user.visible_message("[user] unwrenches the solar assembly from its place.", "You unwrench the solar assembly from its place.")
- W.play_tool_sound(src, 75)
- randomise_offset(random_offset)
- return 1
+ set_anchored(!anchored)
+ user.visible_message("[user] [anchored ? null : "un"]wrenches the solar assembly into place.", "You [anchored ? null : "un"]wrench the solar assembly into place.")
+ W.play_tool_sound(src, 75)
+ return TRUE
if(istype(W, /obj/item/stack/sheet/glass) || istype(W, /obj/item/stack/sheet/rglass))
if(!anchored)
@@ -263,22 +261,22 @@
else
to_chat(user, "You need two sheets of glass to put them into a solar panel!")
return
- return 1
+ return TRUE
if(!tracker)
if(istype(W, /obj/item/electronics/tracker))
if(!user.temporarilyRemoveItemFromInventory(W))
return
- tracker = 1
+ tracker = TRUE
qdel(W)
user.visible_message("[user] inserts the electronics into the solar assembly.", "You insert the electronics into the solar assembly.")
- return 1
+ return TRUE
else
if(W.tool_behaviour == TOOL_CROWBAR)
new /obj/item/electronics/tracker(src.loc)
- tracker = 0
+ tracker = FALSE
user.visible_message("[user] takes out the electronics from the solar assembly.", "You take out the electronics from the solar assembly.")
- return 1
+ return TRUE
return ..()
//
@@ -414,7 +412,7 @@
A.circuit = M
A.state = 3
A.icon_state = "3"
- A.anchored = TRUE
+ A.set_anchored(TRUE)
qdel(src)
else
to_chat(user, "You disconnect the monitor.")
@@ -425,7 +423,7 @@
A.circuit = M
A.state = 4
A.icon_state = "4"
- A.anchored = TRUE
+ A.set_anchored(TRUE)
qdel(src)
else if(user.a_intent != INTENT_HARM && !(I.item_flags & NOBLUDGEON))
attack_hand(user)
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 1a1ce943177..cc023a7a683 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -840,7 +840,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
dust_mob(user, cause = "hand")
/obj/machinery/power/supermatter_crystal/proc/dust_mob(mob/living/nom, vis_msg, mob_msg, cause)
- if(nom.incorporeal_move || nom.status_flags & GODMODE)
+ if(nom.incorporeal_move || nom.status_flags & GODMODE) //try to keep supermatter sliver's + hemostat's dust conditions in sync with this too
return
if(!vis_msg)
vis_msg = "[nom] reaches out and touches [src], inducing a resonance... [nom.p_their()] body starts to glow and burst into flames before flashing into dust!"
diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm
index 577014b1832..9619a3a73fb 100644
--- a/code/modules/power/tracker.dm
+++ b/code/modules/power/tracker.dm
@@ -50,7 +50,7 @@
S = new /obj/item/solar_assembly(src)
S.glass_type = /obj/item/stack/sheet/glass
S.tracker = 1
- S.anchored = TRUE
+ S.set_anchored(TRUE)
S.forceMove(src)
/obj/machinery/power/tracker/crowbar_act(mob/user, obj/item/I)
diff --git a/code/modules/projectiles/ammunition/_ammunition.dm b/code/modules/projectiles/ammunition/_ammunition.dm
index 9d303245688..039fd3a9769 100644
--- a/code/modules/projectiles/ammunition/_ammunition.dm
+++ b/code/modules/projectiles/ammunition/_ammunition.dm
@@ -46,6 +46,25 @@
icon_state = "[initial(icon_state)][BB ? "-live" : ""]"
desc = "[initial(desc)][BB ? "" : " This one is spent."]"
+/*
+ * On accidental consumption, 'spend' the ammo, and add in some gunpowder
+ */
+/obj/item/ammo_casing/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ if(BB)
+ BB = null
+ update_icon()
+ var/obj/item/reagent_containers/food/snacks/S = source_item
+ if(istype(S))
+ if(S.reagents)
+ S.reagents.add_reagent(/datum/reagent/gunpowder, S.reagents.total_volume*(2/3))
+ if(S.tastes?.len)
+ S.tastes += "salt"
+ S.tastes["salt"] = 3
+
+ M.reagents?.add_reagent(/datum/reagent/gunpowder, 3)
+
+ return ..()
+
//proc to magically refill a casing with a new projectile
/obj/item/ammo_casing/proc/newshot() //For energy weapons, syringe gun, shotgun shells and wands (!).
if(!BB)
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index cc5122052db..8ffa5b51a7a 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -38,7 +38,7 @@
/obj/item/ammo_box/c38/dumdum
name = "speed loader (.38 DumDum)"
- desc = "Designed to quickly reload revolvers. DumDum bullets shatter on impact and shred the target's innards, likely getting caught inside."
+ desc = "Designed to quickly reload revolvers. These rounds expand on impact, allowing them to shred the target and cause massive bleeding. Very weak against armor and distant targets."
ammo_type = /obj/item/ammo_casing/c38/dumdum
/obj/item/ammo_box/c38/hotshot
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index a1f58f00d6f..fc0e58659df 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -179,7 +179,7 @@
/obj/item/gun/afterattack(atom/target, mob/living/user, flag, params)
. = ..()
- if(!target)
+ if(QDELETED(target))
return
if(firing_burst)
return
@@ -198,7 +198,8 @@
return
if(iscarbon(target))
var/mob/living/carbon/C = target
- for(var/datum/wound/W in C.all_wounds)
+ for(var/i in C.all_wounds)
+ var/datum/wound/W = i
if(W.try_treating(src, user))
return // another coward cured!
diff --git a/code/modules/projectiles/guns/ballistic/rifle.dm b/code/modules/projectiles/guns/ballistic/rifle.dm
index 980a2722e43..1da8baa8024 100644
--- a/code/modules/projectiles/guns/ballistic/rifle.dm
+++ b/code/modules/projectiles/guns/ballistic/rifle.dm
@@ -16,11 +16,11 @@
bolt_drop_sound = 'sound/weapons/gun/rifle/bolt_in.ogg'
tac_reloads = FALSE
-obj/item/gun/ballistic/rifle/update_overlays()
+/obj/item/gun/ballistic/rifle/update_overlays()
. = ..()
. += "[icon_state]_bolt[bolt_locked ? "_locked" : ""]"
-obj/item/gun/ballistic/rifle/rack(mob/user = null)
+/obj/item/gun/ballistic/rifle/rack(mob/user = null)
if (bolt_locked == FALSE)
to_chat(user, "You open the bolt of \the [src].")
playsound(src, rack_sound, rack_sound_volume, rack_sound_vary)
@@ -30,12 +30,12 @@ obj/item/gun/ballistic/rifle/rack(mob/user = null)
return
drop_bolt(user)
-obj/item/gun/ballistic/rifle/can_shoot()
+/obj/item/gun/ballistic/rifle/can_shoot()
if (bolt_locked)
return FALSE
return ..()
-obj/item/gun/ballistic/rifle/attackby(obj/item/A, mob/user, params)
+/obj/item/gun/ballistic/rifle/attackby(obj/item/A, mob/user, params)
if (!bolt_locked)
to_chat(user, "The bolt is closed!")
return
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index d279ef9cf6c..c08bd8e6afa 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -128,7 +128,7 @@
flags_1 = CONDUCT_1
attack_verb = list("attacked", "slashed", "cut", "sliced")
force = 12
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
can_charge = FALSE
heat = 3800
diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm
index b5135d66bd4..2046ecf1d22 100644
--- a/code/modules/projectiles/guns/magic/staff.dm
+++ b/code/modules/projectiles/guns/magic/staff.dm
@@ -87,7 +87,7 @@
force = 20
armour_penetration = 75
block_chance = 50
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
max_charges = 4
/obj/item/gun/magic/staff/spellblade/Initialize()
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index d18be3b119e..ea5fdbde6b6 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -11,6 +11,7 @@
pass_flags = PASSTABLE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
movement_type = FLYING
+ wound_bonus = CANT_WOUND // can't wound by default
//The sound this plays on impact.
var/hitsound = 'sound/weapons/pierce.ogg'
var/hitsound_wall = ""
@@ -124,23 +125,36 @@
var/temporary_unstoppable_movement = FALSE
- ///If defined, on hit we create an item of this type then call hitby() on the hit target with this
+ ///If defined, on hit we create an item of this type then call hitby() on the hit target with this, mainly used for embedding items (bullets) in targets
var/shrapnel_type
+ ///If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target
+ var/list/embedding
+
///If TRUE, hit mobs even if they're on the floor and not our target
var/hit_stunned_targets = FALSE
- wound_bonus = CANT_WOUND
- /// For telling whether we want to roll for bone breaking or lacerations if we're bothering with wounds
- var/sharpness = FALSE
+ ///For what kind of brute wounds we're rolling for, if we're doing such a thing. Lasers obviously don't care since they do burn instead.
+ var/sharpness = SHARP_NONE
+ ///How much we want to drop both wound_bonus and bare_wound_bonus (to a minimum of 0 for the latter) per tile, for falloff purposes
+ var/wound_falloff_tile
+ ///How much we want to drop the embed_chance value, if we can embed, per tile, for falloff purposes
+ var/embed_falloff_tile
/obj/projectile/Initialize()
. = ..()
permutated = list()
decayedRange = range
+ if(embedding)
+ updateEmbedding()
/obj/projectile/proc/Range()
range--
+ if(wound_bonus != CANT_WOUND)
+ wound_bonus += wound_falloff_tile
+ bare_wound_bonus = max(0, bare_wound_bonus + wound_falloff_tile)
+ if(embedding)
+ embedding["embed_chance"] += embed_falloff_tile
if(range <= 0 && loc)
on_range()
@@ -743,3 +757,23 @@
/obj/projectile/experience_pressure_difference()
return
+
+///Like [/obj/item/proc/updateEmbedding] but for projectiles instead, call this when you want to add embedding or update the stats on the embedding element
+/obj/projectile/proc/updateEmbedding()
+ if(!shrapnel_type || !LAZYLEN(embedding))
+ return
+
+ AddElement(/datum/element/embed,\
+ embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\
+ fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\
+ pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\
+ pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\
+ remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\
+ rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\
+ ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\
+ impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\
+ jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\
+ jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\
+ pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT),\
+ projectile_payload = shrapnel_type)
+ return TRUE
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm
index b76ff677c56..8dac813f8e5 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/bullets.dm
@@ -6,5 +6,13 @@
nodamage = FALSE
flag = "bullet"
hitsound_wall = "ricochet"
- sharpness = TRUE
+ sharpness = SHARP_POINTY
impact_effect_type = /obj/effect/temp_visual/impact_effect
+ shrapnel_type = /obj/item/shrapnel/bullet
+ embedding = list(embed_chance=15, fall_chance=2, jostle_chance=0, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.5, pain_mult=3, rip_time=10)
+ wound_falloff_tile = -5
+ embed_falloff_tile = -5
+
+/obj/projectile/bullet/smite
+ name = "divine retribution"
+ damage = 10
diff --git a/code/modules/projectiles/projectile/bullets/lmg.dm b/code/modules/projectiles/projectile/bullets/lmg.dm
index a6e2f3386d0..e05240a7369 100644
--- a/code/modules/projectiles/projectile/bullets/lmg.dm
+++ b/code/modules/projectiles/projectile/bullets/lmg.dm
@@ -25,8 +25,10 @@
/obj/projectile/bullet/mm712x82
name = "7.12x82mm bullet"
- damage = 45
+ damage = 40
armour_penetration = 5
+ wound_bonus = -50
+ wound_falloff_tile = 0
/obj/projectile/bullet/mm712x82_ap
name = "7.12x82mm armor-piercing bullet"
@@ -35,8 +37,12 @@
/obj/projectile/bullet/mm712x82_hp
name = "7.12x82mm hollow-point bullet"
- damage = 60
+ damage = 50
armour_penetration = -60
+ sharpness = SHARP_EDGED
+ wound_bonus = -40
+ bare_wound_bonus = 30
+ wound_falloff_tile = -8
/obj/projectile/bullet/incendiary/mm712x82
name = "7.12x82mm incendiary bullet"
@@ -50,4 +56,4 @@
ricochet_chance = 60
ricochet_auto_aim_range = 4
ricochet_incidence_leeway = 35
-
+ wound_bonus = -50
diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm
index 1e53db3cdbd..0752c0f8533 100644
--- a/code/modules/projectiles/projectile/bullets/pistol.dm
+++ b/code/modules/projectiles/projectile/bullets/pistol.dm
@@ -3,11 +3,13 @@
/obj/projectile/bullet/c9mm
name = "9mm bullet"
damage = 30
+ embedding = list(embed_chance=15, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
/obj/projectile/bullet/c9mm_ap
name = "9mm armor-piercing bullet"
damage = 27
armour_penetration = 40
+ embedding = null
/obj/projectile/bullet/c9mm_hp
name = "9mm hollow-point bullet"
diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm
index 0c713426365..dc7b56d77ae 100644
--- a/code/modules/projectiles/projectile/bullets/revolver.dm
+++ b/code/modules/projectiles/projectile/bullets/revolver.dm
@@ -19,8 +19,9 @@
ricochet_chance = 50
ricochet_auto_aim_angle = 10
ricochet_auto_aim_range = 3
- wound_bonus = -35
- sharpness = TRUE
+ wound_bonus = -20
+ bare_wound_bonus = 10
+ embedding = list(embed_chance=15, fall_chance=2, jostle_chance=2, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=3, jostle_pain_mult=5, rip_time=10)
/obj/projectile/bullet/c38/match
name = ".38 Match bullet"
@@ -42,14 +43,21 @@
ricochet_chance = 130
ricochet_decay_damage = 0.8
shrapnel_type = NONE
+ sharpness = SHARP_NONE
+ embedding = null
+// premium .38 ammo from cargo, weak against armor, lower base damage, but excellent at embedding and causing slice wounds at close range
/obj/projectile/bullet/c38/dumdum
name = ".38 DumDum bullet"
damage = 15
armour_penetration = -30
ricochets_max = 0
- wound_bonus = 0
- shrapnel_type = /obj/item/shrapnel/bullet/c38/dumdum
+ sharpness = SHARP_EDGED
+ wound_bonus = 20
+ bare_wound_bonus = 20
+ embedding = list(embed_chance=75, fall_chance=3, jostle_chance=4, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+ wound_falloff_tile = -5
+ embed_falloff_tile = -15
/obj/projectile/bullet/c38/trac
name = ".38 TRAC bullet"
@@ -98,6 +106,7 @@
/obj/projectile/bullet/a357
name = ".357 bullet"
damage = 60
+ wound_bonus = -70
// admin only really, for ocelot memes
/obj/projectile/bullet/a357/match
diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm
index 1f596e75f29..d1794ef3036 100644
--- a/code/modules/projectiles/projectile/bullets/rifle.dm
+++ b/code/modules/projectiles/projectile/bullets/rifle.dm
@@ -4,6 +4,7 @@
name = "5.56mm bullet"
damage = 35
armour_penetration = 30
+ wound_bonus = -40
/obj/projectile/bullet/a556/phasic
name = "5.56mm phasic bullet"
@@ -17,6 +18,8 @@
/obj/projectile/bullet/a762
name = "7.62 bullet"
damage = 60
+ wound_bonus = -35
+ wound_falloff_tile = 0
/obj/projectile/bullet/a762_enchanted
name = "enchanted 7.62 bullet"
diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm
index 70ab3dd7aa3..4128eb39d22 100644
--- a/code/modules/projectiles/projectile/bullets/shotgun.dm
+++ b/code/modules/projectiles/projectile/bullets/shotgun.dm
@@ -1,23 +1,26 @@
/obj/projectile/bullet/shotgun_slug
name = "12g shotgun slug"
- damage = 60
+ damage = 50
+ sharpness = SHARP_POINTY
+ wound_bonus = 0
/obj/projectile/bullet/shotgun_slug/executioner
name = "executioner slug" // admin only, can dismember limbs
- sharpness = TRUE
- wound_bonus = 0
+ sharpness = SHARP_EDGED
+ wound_bonus = 80
/obj/projectile/bullet/shotgun_slug/pulverizer
name = "pulverizer slug" // admin only, can crush bones
- sharpness = FALSE
- wound_bonus = 0
+ sharpness = SHARP_NONE
+ wound_bonus = 80
/obj/projectile/bullet/shotgun_beanbag
name = "beanbag slug"
damage = 10
stamina = 55
wound_bonus = 20
- sharpness = FALSE
+ sharpness = SHARP_NONE
+ embedding = null
/obj/projectile/bullet/incendiary/shotgun
name = "incendiary slug"
@@ -68,18 +71,22 @@
return BULLET_ACT_HIT
/obj/projectile/bullet/pellet
- var/tile_dropoff = 0.75
+ var/tile_dropoff = 0.45
var/tile_dropoff_s = 0.5
/obj/projectile/bullet/pellet/shotgun_buckshot
name = "buckshot pellet"
- damage = 12.5
- wound_bonus = -10
+ damage = 7.5
+ wound_bonus = 5
+ bare_wound_bonus = 5
+ wound_falloff_tile = -2.5 // low damage + additional dropoff will already curb wounding potential anything past point blank
/obj/projectile/bullet/pellet/shotgun_rubbershot
name = "rubbershot pellet"
damage = 3
stamina = 11
+ sharpness = SHARP_NONE
+ embedding = null
/obj/projectile/bullet/pellet/shotgun_incapacitate
name = "incapacitating pellet"
@@ -96,8 +103,10 @@
qdel(src)
/obj/projectile/bullet/pellet/shotgun_improvised
- tile_dropoff = 0.55 //Come on it does 6 damage don't be like that.
+ tile_dropoff = 0.35 //Come on it does 6 damage don't be like that.
damage = 6
+ wound_bonus = 0
+ bare_wound_bonus = 7.5
/obj/projectile/bullet/pellet/shotgun_improvised/Initialize()
. = ..()
diff --git a/code/modules/projectiles/projectile/bullets/smg.dm b/code/modules/projectiles/projectile/bullets/smg.dm
index faa0fd98ca2..3cdf3de789e 100644
--- a/code/modules/projectiles/projectile/bullets/smg.dm
+++ b/code/modules/projectiles/projectile/bullets/smg.dm
@@ -2,7 +2,9 @@
/obj/projectile/bullet/c45
name = ".45 bullet"
- damage = 35
+ damage = 30
+ wound_bonus = -10
+ wound_falloff_tile = -10
/obj/projectile/bullet/c45_ap
name = ".45 armor-piercing bullet"
@@ -19,11 +21,15 @@
/obj/projectile/bullet/c46x30mm
name = "4.6x30mm bullet"
damage = 20
+ wound_bonus = -5
+ bare_wound_bonus = 5
+ embed_falloff_tile = -4
/obj/projectile/bullet/c46x30mm_ap
name = "4.6x30mm armor-piercing bullet"
damage = 15
armour_penetration = 40
+ embedding = null
/obj/projectile/bullet/incendiary/c46x30mm
name = "4.6x30mm incendiary bullet"
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index 41ed6f59d5b..4bf005df06d 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -491,11 +491,11 @@
var/list/cached_required_catalysts = C.required_catalysts
var/total_required_catalysts = cached_required_catalysts.len
var/total_matching_catalysts= 0
- var/matching_container = 0
- var/matching_other = 0
+ var/matching_container = FALSE
+ var/matching_other = FALSE
var/required_temp = C.required_temp
var/is_cold_recipe = C.is_cold_recipe
- var/meets_temp_requirement = 0
+ var/meets_temp_requirement = FALSE
for(var/B in cached_required_reagents)
if(!has_reagent(B, cached_required_reagents[B]))
@@ -507,29 +507,28 @@
total_matching_catalysts++
if(cached_my_atom)
if(!C.required_container)
- matching_container = 1
-
+ matching_container = TRUE
else
if(cached_my_atom.type == C.required_container)
- matching_container = 1
+ matching_container = TRUE
if (isliving(cached_my_atom) && !C.mob_react) //Makes it so certain chemical reactions don't occur in mobs
- return
+ matching_container = FALSE
if(!C.required_other)
- matching_other = 1
+ matching_other = TRUE
else if(istype(cached_my_atom, /obj/item/slime_extract))
var/obj/item/slime_extract/M = cached_my_atom
if(M.Uses > 0) // added a limit to slime cores -- Muskets requested this
- matching_other = 1
+ matching_other = TRUE
else
if(!C.required_container)
- matching_container = 1
+ matching_container = TRUE
if(!C.required_other)
- matching_other = 1
+ matching_other = TRUE
if(required_temp == 0 || (is_cold_recipe && chem_temp <= required_temp) || (!is_cold_recipe && chem_temp >= required_temp))
- meets_temp_requirement = 1
+ meets_temp_requirement = TRUE
if(total_matching_reagents == total_required_reagents && total_matching_catalysts == total_required_catalysts && matching_container && matching_other && meets_temp_requirement)
possible_reactions += C
@@ -923,7 +922,7 @@ Needs matabolizing takes into consideration if the chemical is matabolizing when
* Arguments:
* * minimum_percent - the lower the minimum percent, the more sensitive the message is.
*/
-/datum/reagents/proc/generate_taste_message(minimum_percent=15)
+/datum/reagents/proc/generate_taste_message(minimum_percent=15,mob/living/taster)
var/list/out = list()
var/list/tastes = list() //descriptor = strength
if(minimum_percent <= 100)
@@ -931,22 +930,12 @@ Needs matabolizing takes into consideration if the chemical is matabolizing when
if(!R.taste_mult)
continue
- if(istype(R, /datum/reagent/consumable/nutriment))
- var/list/taste_data = R.data
- for(var/taste in taste_data)
- var/ratio = taste_data[taste]
- var/amount = ratio * R.taste_mult * R.volume
- if(taste in tastes)
- tastes[taste] += amount
- else
- tastes[taste] = amount
- else
- var/taste_desc = R.taste_description
- var/taste_amount = R.volume * R.taste_mult
- if(taste_desc in tastes)
- tastes[taste_desc] += taste_amount
+ var/list/taste_data = R.get_taste_description(taster)
+ for(var/taste in taste_data)
+ if(taste in tastes)
+ tastes[taste] += taste_data[taste] * R.volume * R.taste_mult
else
- tastes[taste_desc] = taste_amount
+ tastes[taste] = taste_data[taste] * R.volume * R.taste_mult
//deal with percentages
// TODO it would be great if we could sort these from strong to weak
var/total_taste = counterlist_sum(tastes)
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index 680edb61f23..12ec7b4de92 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -76,6 +76,7 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
var/list/reagent_removal_skip_list = list()
/datum/reagent/New()
+ SHOULD_CALL_PARENT(TRUE)
. = ..()
if(!addiction_type)
@@ -210,6 +211,11 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
if(!mytray)
return
+/// Should return a associative list where keys are taste descriptions and values are strength ratios
+/datum/reagent/proc/get_taste_description(mob/living/taster)
+ return list("[taste_description]" = 1)
+
+
/proc/pretty_string_from_reagent_list(list/reagent_list)
//Convert reagent list to a printable string for logging etc
var/list/rs = list()
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index e77a51fcd98..a79616f4bca 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -103,7 +103,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
taste_description = "dish water"
glass_name = "glass of light beer"
glass_desc = "A freezing pint of watery light beer."
-
+
/datum/reagent/consumable/ethanol/beer/maltliquor
name = "Malt Liquor"
description = "An alcoholic beverage brewed since ancient times on Old Earth. This variety is stronger than usual, super cheap, and super terrible."
@@ -374,6 +374,19 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_desc = "A very classy looking drink."
shot_glass_icon_state = "shotglassred"
+/datum/reagent/consumable/ethanol/wine/on_merge(data)
+ . = ..()
+ if(src.data && data && data["vintage"] != src.data["vintage"])
+ src.data["vintage"] = "mixed wine"
+
+/datum/reagent/consumable/ethanol/wine/get_taste_description(mob/living/taster)
+ if(HAS_TRAIT(taster,TRAIT_WINE_TASTER))
+ if(data && data["vintage"])
+ return list("[data["vintage"]]" = 1)
+ else
+ return list("synthetic wine"=1)
+ return ..()
+
/datum/reagent/consumable/ethanol/lizardwine
name = "Lizard wine"
description = "An alcoholic beverage from Space China, made by infusing lizard tails in ethanol."
@@ -683,14 +696,14 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/beepsky_smash/on_mob_metabolize(mob/living/carbon/M)
if(HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE))
metabolization_rate = 0.8
- if(!HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind != null && !HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
B = new()
M.gain_trauma(B, TRAUMA_RESILIENCE_ABSOLUTE)
..()
/datum/reagent/consumable/ethanol/beepsky_smash/on_mob_life(mob/living/carbon/M)
M.Jitter(2)
- if(HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind != null && HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
M.adjustStaminaLoss(-10, 0)
if(prob(20))
new /datum/hallucination/items_other(M)
@@ -705,7 +718,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
return ..()
/datum/reagent/consumable/ethanol/beepsky_smash/overdose_start(mob/living/carbon/M)
- if(!HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind != null && !HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
M.gain_trauma(/datum/brain_trauma/mild/phobia/security, TRAUMA_RESILIENCE_BASIC)
/datum/reagent/consumable/ethanol/irish_cream
@@ -1521,7 +1534,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/quadruple_sec/on_mob_life(mob/living/carbon/M)
//Securidrink in line with the Screwdriver for engineers or Nothing for mimes
- if(HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind != null && HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
M.heal_bodypart_damage(1, 1)
M.adjustBruteLoss(-2,0)
. = 1
@@ -1540,7 +1553,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/quintuple_sec/on_mob_life(mob/living/carbon/M)
//Securidrink in line with the Screwdriver for engineers or Nothing for mimes but STRONG..
- if(HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind != null && HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
M.heal_bodypart_damage(2,2,2)
M.adjustBruteLoss(-5,0)
M.adjustOxyLoss(-5,0)
@@ -2154,7 +2167,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_desc = "boozy Catholicism in a glass."
/datum/reagent/consumable/ethanol/trappist/on_mob_life(mob/living/carbon/M)
- if(M.mind.holy_role)
+ if(M.mind?.holy_role)
M.adjustFireLoss(-2.5, 0)
M.jitteriness = max(0, M.jitteriness-1)
M.stuttering = max(0, M.stuttering-1)
diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
index b655ae30568..f6cd5e84129 100644
--- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
@@ -406,13 +406,13 @@
#undef issyrinormusc
/******COMBOS******/
/*Suffix: Combo of healing, prob gonna get wack REAL fast*/
-/datum/reagent/medicine/c2/instabitaluri
- name = "Synthflesh (Instabitaluri)"
+/datum/reagent/medicine/c2/synthflesh
+ name = "Synthflesh"
description = "Heals brute and burn damage at the cost of toxicity (66% of damage healed). Touch application only."
reagent_state = LIQUID
color = "#FFEBEB"
-/datum/reagent/medicine/c2/instabitaluri/expose_mob(mob/living/M, method=TOUCH, reac_volume,show_message = 1)
+/datum/reagent/medicine/c2/synthflesh/expose_mob(mob/living/M, method=TOUCH, reac_volume,show_message = 1)
if(iscarbon(M))
var/mob/living/carbon/carbies = M
if (carbies.stat == DEAD)
@@ -420,13 +420,14 @@
if(method in list(PATCH, TOUCH, VAPOR))
var/harmies = min(carbies.getBruteLoss(),carbies.adjustBruteLoss(-1.25 * reac_volume)*-1)
var/burnies = min(carbies.getFireLoss(),carbies.adjustFireLoss(-1.25 * reac_volume)*-1)
- for(var/datum/wound/burn/burn_wound in carbies.all_wounds)
- burn_wound.regenerate_flesh(reac_volume)
+ for(var/i in carbies.all_wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.on_synthflesh(reac_volume)
carbies.adjustToxLoss((harmies+burnies)*0.66)
if(show_message)
to_chat(carbies, "You feel your burns and bruises healing! It stings like hell!")
SEND_SIGNAL(carbies, COMSIG_ADD_MOOD_EVENT, "painful_medicine", /datum/mood_event/painful_medicine)
- if(HAS_TRAIT_FROM(M, TRAIT_HUSK, "burn") && carbies.getFireLoss() < THRESHOLD_UNHUSK && (carbies.reagents.get_reagent_amount(/datum/reagent/medicine/c2/instabitaluri) + reac_volume >= 100))
+ if(HAS_TRAIT_FROM(M, TRAIT_HUSK, "burn") && carbies.getFireLoss() < THRESHOLD_UNHUSK && (carbies.reagents.get_reagent_amount(/datum/reagent/medicine/c2/synthflesh) + reac_volume >= 100))
carbies.cure_husk("burn")
carbies.visible_message("A rubbery liquid coats [carbies]'s burns. [carbies] looks a lot healthier!") //we're avoiding using the phrases "burnt flesh" and "burnt skin" here because carbies could be a skeleton or a golem or something
..()
diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
index 19aa060636a..a94b4f7130c 100644
--- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
@@ -132,6 +132,25 @@
. = 1
..()
+// i googled "natural coagulant" and a couple of results came up for banana peels, so after precisely 30 more seconds of research, i now dub grinding banana peels good for your blood
+/datum/reagent/consumable/banana_peel
+ name = "Pulped Banana Peel"
+ description = "Okay, so you put a banana peel in a grinder... Why, exactly?"
+ color = "#863333" // rgb: 175, 175, 0
+ reagent_state = SOLID
+ taste_description = "stringy, bitter pulp"
+ glass_name = "glass of banana peel pulp"
+ glass_desc = "Okay, so you put a banana peel in a grinder... Why, exactly?"
+
+/datum/reagent/consumable/baked_banana_peel
+ name = "Baked Banana Peel Powder"
+ description = "You took a banana peel... pulped it... baked it... Where are you going with this?"
+ color = "#863333" // rgb: 175, 175, 0
+ reagent_state = SOLID
+ taste_description = "bitter powder"
+ glass_name = "glass of banana peel powder"
+ description = "You took a banana peel... pulped it... baked it... Where are you going with this?"
+
/datum/reagent/consumable/nothing
name = "Nothing"
description = "Absolutely nothing."
@@ -525,7 +544,7 @@
/datum/reagent/consumable/pwr_game/on_mob_life(mob/living/carbon/M)
M.adjust_bodytemperature(-8 * TEMPERATURE_DAMAGE_COEFFICIENT, M.get_body_temp_normal())
if(prob(10))
- M?.mind.adjust_experience(/datum/skill/gaming, 5)
+ M.mind?.adjust_experience(/datum/skill/gaming, 5)
..()
/datum/reagent/consumable/shamblers
@@ -690,28 +709,6 @@
..()
. = 1
-/datum/reagent/consumable/chocolatepudding
- name = "Chocolate Pudding"
- description = "A great dessert for chocolate lovers."
- color = "#800000"
- quality = DRINK_VERYGOOD
- nutriment_factor = 4 * REAGENTS_METABOLISM
- taste_description = "sweet chocolate"
- glass_icon_state = "chocolatepudding"
- glass_name = "chocolate pudding"
- glass_desc = "Tasty."
-
-/datum/reagent/consumable/vanillapudding
- name = "Vanilla Pudding"
- description = "A great dessert for vanilla lovers."
- color = "#FAFAD2"
- quality = DRINK_VERYGOOD
- nutriment_factor = 4 * REAGENTS_METABOLISM
- taste_description = "sweet vanilla"
- glass_icon_state = "vanillapudding"
- glass_name = "vanilla pudding"
- glass_desc = "Tasty."
-
/datum/reagent/consumable/cherryshake
name = "Cherry Shake"
description = "A cherry flavored milkshake."
@@ -921,11 +918,11 @@
/datum/reagent/consumable/bungojuice
name = "Bungo Juice"
color = "#F9E43D"
- description = "Exotic! You feel like you are on vactation already."
+ description = "Exotic! You feel like you are on vacation already."
taste_description = "succulent bungo"
glass_icon_state = "glass_yellow"
glass_name = "glass of bungo juice"
- glass_desc = "Exotic! You feel like you are on vactation already."
+ glass_desc = "Exotic! You feel like you are on vacation already."
/datum/reagent/consumable/prunomix
name = "pruno mixture"
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index 2e10f5be432..84aa0ee6832 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -418,7 +418,7 @@
/datum/reagent/drug/happiness/addiction_act_stage1(mob/living/M)// all work and no play makes jack a dull boy
var/datum/component/mood/mood = M.GetComponent(/datum/component/mood)
- mood.setSanity(min(mood.sanity, SANITY_DISTURBED))
+ mood?.setSanity(min(mood.sanity, SANITY_DISTURBED))
M.Jitter(5)
if(prob(20))
M.emote(pick("twitch","laugh","frown"))
@@ -426,7 +426,7 @@
/datum/reagent/drug/happiness/addiction_act_stage2(mob/living/M)
var/datum/component/mood/mood = M.GetComponent(/datum/component/mood)
- mood.setSanity(min(mood.sanity, SANITY_UNSTABLE))
+ mood?.setSanity(min(mood.sanity, SANITY_UNSTABLE))
M.Jitter(10)
if(prob(30))
M.emote(pick("twitch","laugh","frown"))
@@ -434,7 +434,7 @@
/datum/reagent/drug/happiness/addiction_act_stage3(mob/living/M)
var/datum/component/mood/mood = M.GetComponent(/datum/component/mood)
- mood.setSanity(min(mood.sanity, SANITY_CRAZY))
+ mood?.setSanity(min(mood.sanity, SANITY_CRAZY))
M.Jitter(15)
if(prob(40))
M.emote(pick("twitch","laugh","frown"))
@@ -442,7 +442,7 @@
/datum/reagent/drug/happiness/addiction_act_stage4(mob/living/carbon/human/M)
var/datum/component/mood/mood = M.GetComponent(/datum/component/mood)
- mood.setSanity(SANITY_INSANE)
+ mood?.setSanity(SANITY_INSANE)
M.Jitter(20)
if(prob(50))
M.emote(pick("twitch","laugh","frown"))
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index 52a3bade891..02d224baffb 100755
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -95,6 +95,9 @@
data = taste_amounts
+/datum/reagent/consumable/nutriment/get_taste_description(mob/living/taster)
+ return data
+
/datum/reagent/consumable/nutriment/vitamin
name = "Vitamin"
description = "All the best vitamins, minerals, and carbohydrates the body needs in pure form."
@@ -798,3 +801,25 @@
color = "#78280A" // rgb: 120 40, 10
taste_mult = 2.5 //sugar's 1.5, capsacin's 1.5, so a good middle ground.
taste_description = "smokey sweetness"
+
+/datum/reagent/consumable/chocolatepudding
+ name = "Chocolate Pudding"
+ description = "A great dessert for chocolate lovers."
+ color = "#800000"
+ quality = DRINK_VERYGOOD
+ nutriment_factor = 4 * REAGENTS_METABOLISM
+ taste_description = "sweet chocolate"
+ glass_icon_state = "chocolatepudding"
+ glass_name = "chocolate pudding"
+ glass_desc = "Tasty."
+
+/datum/reagent/consumable/vanillapudding
+ name = "Vanilla Pudding"
+ description = "A great dessert for vanilla lovers."
+ color = "#FAFAD2"
+ quality = DRINK_VERYGOOD
+ nutriment_factor = 4 * REAGENTS_METABOLISM
+ taste_description = "sweet vanilla"
+ glass_icon_state = "vanillapudding"
+ glass_name = "vanilla pudding"
+ glass_desc = "Tasty."
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 325ef4646b2..f6d5385100a 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -151,8 +151,8 @@
M.adjustToxLoss(-power, 0, TRUE) //heals TOXINLOVERs
M.adjustCloneLoss(-power, 0)
for(var/i in M.all_wounds)
- var/datum/wound/W = i
- W.on_xadone(power)
+ var/datum/wound/iter_wound = i
+ iter_wound.on_xadone(power)
REMOVE_TRAIT(M, TRAIT_DISFIGURED, TRAIT_GENERIC) //fixes common causes for disfiguration
. = 1
metabolization_rate = REAGENTS_METABOLISM * (0.00001 * (M.bodytemperature ** 2) + 0.5)
@@ -204,8 +204,8 @@
M.adjustToxLoss(-power, 0, TRUE)
M.adjustCloneLoss(-power, 0)
for(var/i in M.all_wounds)
- var/datum/wound/W = i
- W.on_xadone(power)
+ var/datum/wound/iter_wound = i
+ iter_wound.on_xadone(power)
REMOVE_TRAIT(M, TRAIT_DISFIGURED, TRAIT_GENERIC)
. = 1
..()
@@ -272,7 +272,7 @@
/datum/reagent/medicine/salglu_solution
name = "Saline-Glucose Solution"
- description = "Has a 33% chance per metabolism cycle to heal brute and burn damage. Can be used as a temporary blood substitute."
+ description = "Has a 33% chance per metabolism cycle to heal brute and burn damage. Can be used as a temporary blood substitute, as well as slowly speeding blood regeneration."
reagent_state = LIQUID
color = "#DCDCDC"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
@@ -280,6 +280,7 @@
taste_description = "sweetness and salt"
var/last_added = 0
var/maximum_reachable = BLOOD_VOLUME_NORMAL - 10 //So that normal blood regeneration can continue with salglu active
+ var/extra_regen = 0.25 // in addition to acting as temporary blood, also add this much to their actual blood per tick
/datum/reagent/medicine/salglu_solution/on_mob_life(mob/living/carbon/M)
if(last_added)
@@ -289,7 +290,7 @@
var/amount_to_add = min(M.blood_volume, volume*5)
var/new_blood_level = min(M.blood_volume + amount_to_add, maximum_reachable)
last_added = new_blood_level - M.blood_volume
- M.blood_volume = new_blood_level
+ M.blood_volume = new_blood_level + extra_regen
if(prob(33))
M.adjustBruteLoss(-0.5*REM, 0)
M.adjustFireLoss(-0.5*REM, 0)
@@ -569,7 +570,7 @@
/datum/reagent/medicine/morphine
name = "Morphine"
- description = "A painkiller that allows the patient to move at full speed even in bulky objects. Causes drowsiness and eventually unconsciousness in high doses. Overdose will cause a variety of effects, ranging from minor to lethal."
+ description = "A painkiller that allows the patient to move at full speed even when injured. Causes drowsiness and eventually unconsciousness in high doses. Overdose will cause a variety of effects, ranging from minor to lethal."
reagent_state = LIQUID
color = "#A9FBFB"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
@@ -1217,7 +1218,7 @@
M.confused = max(0, M.confused-6)
M.disgust = max(0, M.disgust-6)
var/datum/component/mood/mood = M.GetComponent(/datum/component/mood)
- if(mood.sanity <= SANITY_NEUTRAL) // only take effect if in negative sanity and then...
+ if(mood != null && mood.sanity <= SANITY_NEUTRAL) // only take effect if in negative sanity and then...
mood.setSanity(min(mood.sanity+5, SANITY_NEUTRAL)) // set minimum to prevent unwanted spiking over neutral
..()
. = 1
@@ -1337,10 +1338,57 @@
L.Dizzy(0)
L.Jitter(0)
-// handled in cut wounds process
+// helps bleeding wounds clot faster
/datum/reagent/medicine/coagulant
name = "Sanguirite"
- description = "A coagulant used to help open cuts clot faster."
+ description = "A proprietary coagulant used to help bleeding wounds clot faster."
reagent_state = LIQUID
color = "#bb2424"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
+ overdose_threshold = 20
+ /// How much base clotting we do per bleeding wound, multiplied by the below number for each bleeding wound
+ var/clot_rate = 0.25
+ /// If we have multiple bleeding wounds, we count the number of bleeding wounds, then multiply the clot rate by this^(n) before applying it to each cut, so more cuts = less clotting per cut (though still more total clotting)
+ var/clot_coeff_per_wound = 0.9
+
+/datum/reagent/medicine/coagulant/on_mob_life(mob/living/carbon/M)
+ . = ..()
+ if(!M.blood_volume || !M.all_wounds)
+ return
+
+ var/effective_clot_rate = clot_rate
+
+ for(var/i in M.all_wounds)
+ var/datum/wound/iter_wound = i
+ if(iter_wound.blood_flow)
+ effective_clot_rate *= clot_coeff_per_wound
+
+ for(var/i in M.all_wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.blood_flow = max(0, iter_wound.blood_flow - effective_clot_rate)
+
+/datum/reagent/medicine/coagulant/overdose_process(mob/living/M)
+ . = ..()
+ if(!M.blood_volume)
+ return
+
+ if(prob(15))
+ M.losebreath += rand(2,4)
+ M.adjustOxyLoss(rand(1,3))
+ if(prob(30))
+ to_chat(M, "You can feel your blood clotting up in your veins!")
+ else if(prob(10))
+ to_chat(M, "You feel like your blood has stopped moving!")
+
+ if(prob(50))
+ var/obj/item/organ/lungs/our_lungs = M.getorganslot(ORGAN_SLOT_LUNGS)
+ our_lungs.applyOrganDamage(1)
+ else
+ var/obj/item/organ/heart/our_heart = M.getorganslot(ORGAN_SLOT_HEART)
+ our_heart.applyOrganDamage(1)
+
+// can be synthesized on station rather than bought. made by grinding a banana peel, heating it up, then mixing the banana peel powder with salglu
+/datum/reagent/medicine/coagulant/weak
+ name = "Synthi-Sanguirite"
+ description = "A synthetic coagulant used to help bleeding wounds clot faster. Not quite as effective as name brand Sanguirite, especially on patients with lots of cuts."
+ clot_coeff_per_wound = 0.8
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index fd15da2df22..cd42a1a1c5c 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -201,6 +201,11 @@
M.ExtinguishMob()
..()
+/datum/reagent/water/on_mob_life(mob/living/carbon/M)
+ . = ..()
+ if(M.blood_volume)
+ M.blood_volume += 0.1 // water is good for you!
+
/datum/reagent/water/holywater
name = "Holy Water"
description = "Water blessed by some deity."
@@ -233,6 +238,8 @@
..()
/datum/reagent/water/holywater/on_mob_life(mob/living/carbon/M)
+ if(M.blood_volume)
+ M.blood_volume += 0.1 // water is good for you!
if(!data)
data = list("misc" = 1)
data["misc"]++
@@ -1056,55 +1063,26 @@
color = "#A5F0EE" // rgb: 165, 240, 238
taste_description = "sourness"
reagent_weight = 0.6 //so it sprays further
+ var/clean_types = CLEAN_WASH
/datum/reagent/space_cleaner/expose_obj(obj/O, reac_volume)
- if(istype(O, /obj/effect/decal/cleanable))
- qdel(O)
- else
- if(O)
- O.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- SEND_SIGNAL(O, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
+ O?.wash(clean_types)
/datum/reagent/space_cleaner/expose_turf(turf/T, reac_volume)
if(reac_volume >= 1)
- T.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- SEND_SIGNAL(T, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
- for(var/obj/effect/decal/cleanable/C in T)
- qdel(C)
+ T.wash(clean_types)
+ for(var/am in T)
+ var/atom/movable/movable_content
+ if(ismopable(movable_content)) // Mopables will be cleaned anyways by the turf wash
+ continue
+ movable_content.wash(clean_types)
for(var/mob/living/simple_animal/slime/M in T)
M.adjustToxLoss(rand(5,10))
/datum/reagent/space_cleaner/expose_mob(mob/living/M, method=TOUCH, reac_volume)
if(method == TOUCH || method == VAPOR)
- M.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- if(iscarbon(M))
- var/mob/living/carbon/C = M
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.lip_style)
- H.lip_style = null
- H.update_body()
- for(var/obj/item/I in C.held_items)
- SEND_SIGNAL(I, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
- if(C.wear_mask)
- if(SEND_SIGNAL(C.wear_mask, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD))
- C.update_inv_wear_mask()
- if(ishuman(M))
- var/mob/living/carbon/human/H = C
- if(H.head)
- if(SEND_SIGNAL(H.head, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD))
- H.update_inv_head()
- if(H.wear_suit)
- if(SEND_SIGNAL(H.wear_suit, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD))
- H.update_inv_wear_suit()
- else if(H.w_uniform)
- if(SEND_SIGNAL(H.w_uniform, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD))
- H.update_inv_w_uniform()
- if(H.shoes)
- if(SEND_SIGNAL(H.shoes, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD))
- H.update_inv_shoes()
- SEND_SIGNAL(M, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
+ M.wash(clean_types)
/datum/reagent/space_cleaner/ez_clean
name = "EZ Clean"
@@ -1375,6 +1353,7 @@
description = "An invisible powder. Unfortunately, since it's invisible, it doesn't look like it'd color much of anything..."
else
description = "\An [colorname] powder, used for coloring things [colorname]."
+ return ..()
/datum/reagent/colorful_reagent/powder/red
name = "Red Powder"
@@ -1748,6 +1727,7 @@
/datum/reagent/colorful_reagent/New()
SSticker.OnRoundstart(CALLBACK(src,.proc/UpdateColor))
+ return ..()
/datum/reagent/colorful_reagent/proc/UpdateColor()
color = pick(random_color_list)
@@ -1773,6 +1753,7 @@
/datum/reagent/hair_dye/New()
SSticker.OnRoundstart(CALLBACK(src,.proc/UpdateColor))
+ return ..()
/datum/reagent/hair_dye/proc/UpdateColor()
color = pick(potential_colors)
@@ -2282,6 +2263,7 @@
reagent_state = LIQUID
color = "#D2FFFA"
metabolization_rate = 0.75 * REAGENTS_METABOLISM // 5u (WOUND_DETERMINATION_CRITICAL) will last for ~17 ticks
+ self_consuming = TRUE
/// Whether we've had at least WOUND_DETERMINATION_SEVERE (2.5u) of determination at any given time. No damage slowdown immunity or indication we're having a second wind if it's just a single moderate wound
var/significant = FALSE
diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm
index 3165fe9f4a8..0f834c6c2a3 100644
--- a/code/modules/reagents/chemistry/recipes/medicine.dm
+++ b/code/modules/reagents/chemistry/recipes/medicine.dm
@@ -30,6 +30,18 @@
results = list(/datum/reagent/medicine/salglu_solution = 3)
required_reagents = list(/datum/reagent/consumable/sodiumchloride = 1, /datum/reagent/water = 1, /datum/reagent/consumable/sugar = 1)
+/datum/chemical_reaction/baked_banana_peel
+ results = list(/datum/reagent/consumable/baked_banana_peel = 1)
+ required_temp = 413.15 // if it's good enough for caramel it's good enough for this
+ required_reagents = list(/datum/reagent/consumable/banana_peel = 1)
+ mix_message = "The pulp dries up and takes on a powdery state!"
+ mob_react = FALSE
+
+/datum/chemical_reaction/coagulant_weak
+ results = list(/datum/reagent/medicine/coagulant/weak = 3)
+ required_reagents = list(/datum/reagent/medicine/salglu_solution = 2, /datum/reagent/consumable/baked_banana_peel = 1)
+ mob_react = FALSE
+
/datum/chemical_reaction/mine_salve
results = list(/datum/reagent/medicine/mine_salve = 3)
required_reagents = list(/datum/reagent/fuel/oil = 1, /datum/reagent/water = 1, /datum/reagent/iron = 1)
@@ -38,8 +50,8 @@
results = list(/datum/reagent/medicine/mine_salve = 15)
required_reagents = list(/datum/reagent/toxin/plasma = 5, /datum/reagent/iron = 5, /datum/reagent/consumable/sugar = 1) // A sheet of plasma, a twinkie and a sheet of metal makes four of these
-/datum/chemical_reaction/instabitaluri
- results = list(/datum/reagent/medicine/c2/instabitaluri = 3)
+/datum/chemical_reaction/synthflesh
+ results = list(/datum/reagent/medicine/c2/synthflesh = 3)
required_reagents = list(/datum/reagent/blood = 1, /datum/reagent/carbon = 1, /datum/reagent/medicine/c2/libital = 1)
/datum/chemical_reaction/calomel
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index bb5f6f2e5b3..5f87268d040 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -401,14 +401,14 @@
required_reagents = list(/datum/reagent/stable_plasma = 1, /datum/reagent/uranium/radium = 1, /datum/reagent/drug/space_drugs = 1, /datum/reagent/medicine/cryoxadone = 1, /datum/reagent/consumable/triple_citrus = 1)
/datum/chemical_reaction/life
- required_reagents = list(/datum/reagent/medicine/strange_reagent = 1, /datum/reagent/medicine/c2/instabitaluri = 1, /datum/reagent/blood = 1)
+ required_reagents = list(/datum/reagent/medicine/strange_reagent = 1, /datum/reagent/medicine/c2/synthflesh = 1, /datum/reagent/blood = 1)
required_temp = 374
/datum/chemical_reaction/life/on_reaction(datum/reagents/holder, created_volume)
chemical_mob_spawn(holder, rand(1, round(created_volume, 1)), "Life (hostile)") //defaults to HOSTILE_SPAWN
/datum/chemical_reaction/life_friendly
- required_reagents = list(/datum/reagent/medicine/strange_reagent = 1, /datum/reagent/medicine/c2/instabitaluri = 1, /datum/reagent/consumable/sugar = 1)
+ required_reagents = list(/datum/reagent/medicine/strange_reagent = 1, /datum/reagent/medicine/c2/synthflesh = 1, /datum/reagent/consumable/sugar = 1)
required_temp = 374
/datum/chemical_reaction/life_friendly/on_reaction(datum/reagents/holder, created_volume)
@@ -442,7 +442,7 @@
//water electrolysis
/datum/chemical_reaction/electrolysis
- results = list(/datum/reagent/oxygen = 10, /datum/reagent/hydrogen = 20)
+ results = list(/datum/reagent/oxygen = 1.5, /datum/reagent/hydrogen = 3)
required_reagents = list(/datum/reagent/consumable/liquidelectricity = 1, /datum/reagent/water = 5)
//butterflium
diff --git a/code/modules/reagents/chemistry/recipes/special.dm b/code/modules/reagents/chemistry/recipes/special.dm
index 57b813f086c..9774d3ecfc3 100644
--- a/code/modules/reagents/chemistry/recipes/special.dm
+++ b/code/modules/reagents/chemistry/recipes/special.dm
@@ -54,7 +54,7 @@ GLOBAL_LIST_INIT(food_reagents, build_reagents_to_food()) //reagentid = related
var/list/possible_results = list()
/datum/chemical_reaction/randomized/proc/GenerateRecipe()
- created = world.time
+ created = world.realtime
if(randomize_container)
required_container = pick(possible_containers)
if(randomize_req_temperature)
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index fea4aed8016..fb2e586244f 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -63,6 +63,17 @@
return 0
return 1
+/*
+ * On accidental consumption, transfer a portion of the reagents to the eater and the item it's in, then continue to the base proc (to deal with shattering glass containers)
+ */
+/obj/item/reagent_containers/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ M.losebreath += 2
+ reagents?.trans_to(M, min(15, reagents.total_volume / rand(5,10)), transfered_by = user, method = INGEST)
+ if(source_item?.reagents)
+ reagents.trans_to(source_item, min(source_item.reagents.total_volume / 2, reagents.total_volume / 5), transfered_by = user, method = TOUCH)
+
+ return ..()
+
/obj/item/reagent_containers/ex_act()
if(reagents)
for(var/datum/reagent/R in reagents.reagent_list)
diff --git a/code/modules/reagents/reagent_containers/chem_pack.dm b/code/modules/reagents/reagent_containers/chem_pack.dm
index 4bb64506100..666f812e705 100644
--- a/code/modules/reagents/reagent_containers/chem_pack.dm
+++ b/code/modules/reagents/reagent_containers/chem_pack.dm
@@ -33,7 +33,7 @@
. += "Alt-click to seal it."
-obj/item/reagent_containers/chem_pack/attack_self(mob/user)
+/obj/item/reagent_containers/chem_pack/attack_self(mob/user)
if(sealed)
return
..()
diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm
index b7286eaa239..5bfefbcd12c 100755
--- a/code/modules/reagents/reagent_containers/glass.dm
+++ b/code/modules/reagents/reagent_containers/glass.dm
@@ -48,8 +48,20 @@
log_combat(user, M, "fed", reagents.log_list())
else
to_chat(user, "You swallow a gulp of [src].")
+ SEND_SIGNAL(src, COMSIG_GLASS_DRANK, M, user)
addtimer(CALLBACK(reagents, /datum/reagents.proc/trans_to, M, 5, TRUE, TRUE, FALSE, user, FALSE, INGEST), 5)
playsound(M.loc,'sound/items/drink.ogg', rand(10,50), TRUE)
+ if(iscarbon(M))
+ var/mob/living/carbon/carbon_drinker = M
+ var/list/diseases = carbon_drinker.get_static_viruses()
+ if(LAZYLEN(diseases))
+ var/list/datum/disease/diseases_to_add = list()
+ for(var/d in diseases)
+ var/datum/disease/malady = d
+ if(malady.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS)
+ diseases_to_add += malady
+ if(LAZYLEN(diseases_to_add))
+ AddComponent(/datum/component/infective, diseases_to_add)
/obj/item/reagent_containers/glass/afterattack(obj/target, mob/user, proximity)
. = ..()
@@ -108,6 +120,13 @@
return
..()
+/*
+ * On accidental consumption, make sure the container is partially glass, and continue to the reagent_container proc
+ */
+/obj/item/reagent_containers/glass/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ if(!custom_materials)
+ custom_materials = list(SSmaterials.GetMaterialRef(/datum/material/glass) = 5) //sets it to glass so, later on, it gets picked up by the glass catch (hope it doesn't 'break' things lol)
+ return ..()
/obj/item/reagent_containers/glass/beaker
name = "beaker"
@@ -206,8 +225,8 @@
name = "epinephrine reserve tank (diluted)"
list_reagents = list(/datum/reagent/medicine/epinephrine = 50)
-/obj/item/reagent_containers/glass/beaker/instabitaluri
- list_reagents = list(/datum/reagent/medicine/c2/instabitaluri = 50)
+/obj/item/reagent_containers/glass/beaker/synthflesh
+ list_reagents = list(/datum/reagent/medicine/c2/synthflesh = 50)
/obj/item/reagent_containers/glass/bucket
name = "bucket"
diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm
index 6226e681e66..6711be55a1e 100644
--- a/code/modules/reagents/reagent_containers/hypospray.dm
+++ b/code/modules/reagents/reagent_containers/hypospray.dm
@@ -277,7 +277,14 @@
/obj/item/reagent_containers/hypospray/medipen/ekit
name = "emergency first-aid autoinjector"
- desc = "An epinephrine medipen with trace amounts of coagulants and antibiotics to help stabilize bad cuts and burns."
+ desc = "An epinephrine medipen with extra coagulant and antibiotics to help stabilize bad cuts and burns."
volume = 15
amount_per_transfer_from_this = 15
list_reagents = list(/datum/reagent/medicine/epinephrine = 12, /datum/reagent/medicine/coagulant = 2.5, /datum/reagent/medicine/spaceacillin = 0.5)
+
+/obj/item/reagent_containers/hypospray/medipen/blood_loss
+ name = "hypovolemic-response autoinjector"
+ desc = "A medipen designed to stabilize and rapidly reverse severe bloodloss."
+ volume = 15
+ amount_per_transfer_from_this = 15
+ list_reagents = list(/datum/reagent/medicine/epinephrine = 5, /datum/reagent/medicine/coagulant = 2.5, /datum/reagent/iron = 3.5, /datum/reagent/medicine/salglu_solution = 4)
diff --git a/code/modules/reagents/reagent_containers/medigel.dm b/code/modules/reagents/reagent_containers/medigel.dm
index 78f910aa942..f8a11e186b9 100644
--- a/code/modules/reagents/reagent_containers/medigel.dm
+++ b/code/modules/reagents/reagent_containers/medigel.dm
@@ -88,12 +88,12 @@
current_skin = "burngel"
list_reagents = list(/datum/reagent/medicine/c2/aiuri = 24, /datum/reagent/medicine/granibitaluri = 36)
-/obj/item/reagent_containers/medigel/instabitaluri
- name = "medical gel (instabitaluri)"
- desc = "A medical gel applicator bottle, designed for precision application, with an unscrewable cap. This one contains instabitaluri, a slightly toxic medicine capable of healing both bruises and burns."
+/obj/item/reagent_containers/medigel/synthflesh
+ name = "medical gel (synthflesh)"
+ desc = "A medical gel applicator bottle, designed for precision application, with an unscrewable cap. This one contains synthflesh, a slightly toxic medicine capable of healing both bruises and burns."
icon_state = "synthgel"
current_skin = "synthgel"
- list_reagents = list(/datum/reagent/medicine/c2/instabitaluri = 60)
+ list_reagents = list(/datum/reagent/medicine/c2/synthflesh = 60)
custom_price = 600
/obj/item/reagent_containers/medigel/sterilizine
diff --git a/code/modules/reagents/reagent_containers/patch.dm b/code/modules/reagents/reagent_containers/patch.dm
index b0f2701cecf..0642313f5b5 100644
--- a/code/modules/reagents/reagent_containers/patch.dm
+++ b/code/modules/reagents/reagent_containers/patch.dm
@@ -39,8 +39,8 @@
list_reagents = list(/datum/reagent/medicine/c2/aiuri = 2, /datum/reagent/medicine/granibitaluri = 8)
icon_state = "bandaid_burn"
-/obj/item/reagent_containers/pill/patch/instabitaluri
- name = "instabitaluri patch"
+/obj/item/reagent_containers/pill/patch/synthflesh
+ name = "synthflesh patch"
desc = "Helps with brute and burn injuries. Slightly toxic."
- list_reagents = list(/datum/reagent/medicine/c2/instabitaluri = 20)
+ list_reagents = list(/datum/reagent/medicine/c2/synthflesh = 20)
icon_state = "bandaid_both"
diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm
index 1ae0c2442a9..1766949e89d 100644
--- a/code/modules/reagents/reagent_containers/pill.dm
+++ b/code/modules/reagents/reagent_containers/pill.dm
@@ -73,6 +73,18 @@
reagents.trans_to(target, reagents.total_volume, transfered_by = user)
qdel(src)
+/*
+ * On accidental consumption, consume the pill
+ */
+/obj/item/reagent_containers/pill/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = FALSE)
+ to_chat(M, "You swallow something small. Was that in \the [source_item]?")
+ if(reagents?.total_volume)
+ reagents.trans_to(M, reagents.total_volume, transfered_by = user, method = INGEST)
+
+ source_item?.contents -= src
+ qdel(src)
+ return discover_after
+
/obj/item/reagent_containers/pill/tox
name = "toxins pill"
desc = "Highly toxic."
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index 412f54e4b06..a42895061ce 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -4,6 +4,7 @@
icon = 'icons/obj/janitor.dmi'
icon_state = "sprayer_large"
inhand_icon_state = "cleaner"
+ worn_icon_state = "spraybottle"
lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi'
item_flags = NOBLUDGEON
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index 53c6e2abb18..0aa074a33cd 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -15,6 +15,7 @@
custom_materials = list(/datum/material/iron=10, /datum/material/glass=20)
reagent_flags = TRANSPARENT
custom_price = 150
+ sharpness = SHARP_POINTY
/obj/item/reagent_containers/syringe/Initialize()
. = ..()
@@ -155,6 +156,17 @@
mode = SYRINGE_DRAW
update_icon()
+/*
+ * On accidental consumption, inject the eater with 2/3rd of the syringe and reveal it
+ */
+/obj/item/reagent_containers/syringe/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ to_chat(M, "There's a syringe in \the [source_item]!!")
+ M.apply_damage(5, BRUTE, BODY_ZONE_HEAD)
+ if(reagents?.total_volume)
+ reagents.trans_to(M, round(reagents.total_volume*(2/3)), transfered_by = user, method = INJECT)
+
+ return discover_after
+
/obj/item/reagent_containers/syringe/update_icon_state()
var/rounded_vol = get_rounded_vol()
icon_state = "[rounded_vol]"
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index f8eb5607209..db07523ef1a 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -228,7 +228,7 @@
if(stored)
stored.forceMove(T)
src.transfer_fingerprints_to(stored)
- stored.anchored = FALSE
+ stored.set_anchored(FALSE)
stored.density = TRUE
stored.update_icon()
for(var/atom/movable/AM in src) //out, out, darned crowbar!
diff --git a/code/modules/recycling/disposal/construction.dm b/code/modules/recycling/disposal/construction.dm
index b97d3a77b5f..a548e699b6a 100644
--- a/code/modules/recycling/disposal/construction.dm
+++ b/code/modules/recycling/disposal/construction.dm
@@ -13,12 +13,18 @@
var/obj/pipe_type = /obj/structure/disposalpipe/segment
var/pipename
+/obj/structure/disposalconstruct/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return
+ density = anchorvalue ? initial(pipe_type.density) : FALSE
+
/obj/structure/disposalconstruct/Initialize(loc, _pipe_type, _dir = SOUTH, flip = FALSE, obj/make_from)
. = ..()
if(make_from)
pipe_type = make_from.type
setDir(make_from.dir)
- anchored = TRUE
+ set_anchored(TRUE)
else
if(_pipe_type)
@@ -104,8 +110,7 @@
/obj/structure/disposalconstruct/wrench_act(mob/living/user, obj/item/I)
..()
if(anchored)
- anchored = FALSE
- density = FALSE
+ set_anchored(FALSE)
to_chat(user, "You detach the [pipename] from the underfloor.")
else
var/ispipe = is_pipe() // Indicates if we should change the level of this pipe
@@ -140,8 +145,7 @@
to_chat(user, "The [pipename] requires a trunk underneath it in order to work!")
return TRUE
- anchored = TRUE
- density = initial(pipe_type.density)
+ set_anchored(TRUE)
to_chat(user, "You attach the [pipename] to the underfloor.")
I.play_tool_sound(src, 100)
update_icon()
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index eb9172fef38..a58bb4ddb87 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -328,6 +328,7 @@
desc = "Used to set the destination of properly wrapped packages."
icon = 'icons/obj/device.dmi'
icon_state = "cargotagger"
+ worn_icon_state = "cargotagger"
var/currTag = 0 //Destinations are stored in code\globalvars\lists\flavor_misc.dm
var/locked_destination = FALSE //if true, users can't open the destination tag window to prevent changing the tagger's current destination
w_class = WEIGHT_CLASS_TINY
@@ -382,6 +383,7 @@
desc = "A scanner that lets you tag wrapped items for sale, splitting the profit between you and cargo. Ctrl-Click to clear the registered account."
icon = 'icons/obj/device.dmi'
icon_state = "salestagger"
+ worn_icon_state = "salestagger"
inhand_icon_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
diff --git a/code/modules/research/bepis.dm b/code/modules/research/bepis.dm
index 69cc42b1a54..e76ad7c9ad5 100644
--- a/code/modules/research/bepis.dm
+++ b/code/modules/research/bepis.dm
@@ -4,8 +4,8 @@
#define MACHINE_OPERATION 100000
#define MACHINE_OVERLOAD 500000
-#define MAJOR_THRESHOLD 5500
-#define MINOR_THRESHOLD 3500
+#define MAJOR_THRESHOLD 3000
+#define MINOR_THRESHOLD 2000
#define STANDARD_DEVIATION 1000
/obj/machinery/rnd/bepis
@@ -94,7 +94,7 @@
update_icon_state()
say("Attempting to deposit 0 credits. Aborting.")
return
- deposit_value = clamp(round(deposit_value, 1), 1, 15000)
+ deposit_value = clamp(round(deposit_value, 1), 1, 10000)
if(!account)
say("Cannot find user account. Please swipe a valid ID.")
return
diff --git a/code/modules/research/designs/comp_board_designs.dm b/code/modules/research/designs/comp_board_designs.dm
index cae328fd0ba..b804cd89fc8 100644
--- a/code/modules/research/designs/comp_board_designs.dm
+++ b/code/modules/research/designs/comp_board_designs.dm
@@ -311,3 +311,10 @@
build_path = /obj/item/circuitboard/computer/advanced_camera
category = list("Computer Boards")
departmental_flags = DEPARTMENTAL_FLAG_SECURITY
+
+/datum/design/board/bountypad_control
+ name = "Computer Design (Civilian Bounty Pad Control)"
+ desc = "Allows for the construction of circuit boards used to build a new civilian bounty pad console."
+ id = "bounty_pad_control"
+ build_path = /obj/item/circuitboard/computer/bountypad
+ category = list("Computer Boards")
diff --git a/code/modules/research/designs/limbgrower_designs.dm b/code/modules/research/designs/limbgrower_designs.dm
index 4064592cea2..ae5e84acf70 100644
--- a/code/modules/research/designs/limbgrower_designs.dm
+++ b/code/modules/research/designs/limbgrower_designs.dm
@@ -6,7 +6,7 @@
name = "Left Arm"
id = "leftarm"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 25)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 25)
build_path = /obj/item/bodypart/l_arm
category = list("initial","human","lizard","moth","plasmaman","ethereal")
@@ -14,7 +14,7 @@
name = "Right Arm"
id = "rightarm"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 25)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 25)
build_path = /obj/item/bodypart/r_arm
category = list("initial","human","lizard","moth","plasmaman","ethereal")
@@ -22,7 +22,7 @@
name = "Left Leg"
id = "leftleg"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 25)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 25)
build_path = /obj/item/bodypart/l_leg
category = list("initial","human","lizard","moth","plasmaman","ethereal")
@@ -30,7 +30,7 @@
name = "Right Leg"
id = "rightleg"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 25)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 25)
build_path = /obj/item/bodypart/r_leg
category = list("initial","human","lizard","moth","plasmaman","ethereal")
@@ -40,7 +40,7 @@
name = "Arm Blade"
id = "armblade"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 75)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 75)
build_path = /obj/item/melee/synthetic_arm_blade
category = list("other","emagged")
@@ -48,7 +48,7 @@
name = "Heart"
id = "heart"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 30)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 30)
build_path = /obj/item/organ/heart
category = list("other")
@@ -56,7 +56,7 @@
name = "Lungs"
id = "lungs"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 20)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 20)
build_path = /obj/item/organ/lungs
category = list("other")
@@ -64,7 +64,7 @@
name = "Liver"
id = "liver"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 20)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 20)
build_path = /obj/item/organ/liver
category = list("other")
@@ -72,7 +72,7 @@
name = "Stomach"
id = "stomach"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 15)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 15)
build_path = /obj/item/organ/stomach
category = list("other")
@@ -80,7 +80,7 @@
name = "Appendix"
id = "appendix"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 5) //why would you need this
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 5) //why would you need this
build_path = /obj/item/organ/appendix
category = list("other")
@@ -88,7 +88,7 @@
name = "Eyes"
id = "eyes"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 10)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10)
build_path = /obj/item/organ/eyes
category = list("other")
@@ -96,7 +96,7 @@
name = "Ears"
id = "ears"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 10)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10)
build_path = /obj/item/organ/ears
category = list("other")
@@ -104,6 +104,6 @@
name = "Tongue"
id = "tongue"
build_type = LIMBGROWER
- reagents_list = list(/datum/reagent/medicine/c2/instabitaluri = 10)
+ reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10)
build_path = /obj/item/organ/tongue
category = list("other")
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 2ff5c6270a6..19ba8458e6e 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -230,7 +230,7 @@
id = "bepis"
build_path = /obj/item/circuitboard/machine/bepis
category = list("Research Machinery")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_CARGO
/datum/design/board/protolathe
name = "Machine Design (Protolathe Board)"
@@ -610,15 +610,30 @@
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/board/sheetifier
- name = "Sheetifier"
- desc = "This machine turns weird things into sheets."
+ name = "Machine Design (Sheet-meister 2000)"
+ desc = "The circuit board for a Sheet-meister 2000."
id = "sheetifier"
build_path = /obj/item/circuitboard/machine/sheetifier
category = list ("Misc. Machinery")
/datum/design/board/vendatray
- name = "Vend-a-Tray"
+ name = "Machine Design (Vend-a-Tray)"
desc = "The circuit board for a Vend-a-Tray."
id = "vendatray"
build_path = /obj/item/circuitboard/machine/vendatray
category = list ("Misc. Machinery")
+
+/datum/design/board/bountypad
+ name = "Machine Design (Civilian Bounty Pad)"
+ desc = "The circuit board for a Civilian Bounty Pad."
+ id = "bounty_pad"
+ build_path = /obj/item/circuitboard/machine/bountypad
+ category = list ("Misc. Machinery")
+
+/datum/design/board/skill_station
+ name = "Machine Design (Skill station)"
+ desc = "The circuit board for Skill station."
+ id = "skill_station"
+ build_path = /obj/item/circuitboard/machine/skill_station
+ category = list ("Misc. Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SERVICE
diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm
index fa5e30df7ee..16d132af94f 100644
--- a/code/modules/research/designs/medical_designs.dm
+++ b/code/modules/research/designs/medical_designs.dm
@@ -613,7 +613,7 @@
construction_time = 40
materials = list(/datum/material/iron = 500, /datum/material/glass = 500)
build_path = /obj/item/organ/stomach/cybernetic
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cybernetic_stomach/tier2
@@ -803,7 +803,7 @@
/datum/design/surgery/wing_reconstruction
name = "Wing Reconstruction"
- desc = "An experimental surgical procedure that reconstructs the damaged wings of moth people. Requires Instabitaluri."
+ desc = "An experimental surgical procedure that reconstructs the damaged wings of moth people. Requires Synthflesh."
id = "surgery_wing_reconstruction"
surgery = /datum/surgery/advanced/wing_reconstruction
research_icon_state = "surgery_chest"
diff --git a/code/modules/research/nanites/nanite_chamber.dm b/code/modules/research/nanites/nanite_chamber.dm
index 782d0ef0fdd..73871777689 100644
--- a/code/modules/research/nanites/nanite_chamber.dm
+++ b/code/modules/research/nanites/nanite_chamber.dm
@@ -178,7 +178,7 @@
return TRUE
-/obj/machinery/nanite_chamber/relaymove(mob/user as mob)
+/obj/machinery/nanite_chamber/relaymove(mob/user)
if(user.stat || locked)
if(message_cooldown <= world.time)
message_cooldown = world.time + 50
diff --git a/code/modules/research/nanites/public_chamber.dm b/code/modules/research/nanites/public_chamber.dm
index 946abae9900..625f3cd7bee 100644
--- a/code/modules/research/nanites/public_chamber.dm
+++ b/code/modules/research/nanites/public_chamber.dm
@@ -172,7 +172,7 @@
return TRUE
-/obj/machinery/public_nanite_chamber/relaymove(mob/user as mob)
+/obj/machinery/public_nanite_chamber/relaymove(mob/user)
if(user.stat || locked)
if(message_cooldown <= world.time)
message_cooldown = world.time + 50
diff --git a/code/modules/research/stock_parts.dm b/code/modules/research/stock_parts.dm
index b5383c94864..3006723d902 100644
--- a/code/modules/research/stock_parts.dm
+++ b/code/modules/research/stock_parts.dm
@@ -6,6 +6,7 @@ If you create T5+ please take a pass at mech_fabricator.dm. The parts being good
desc = "Special mechanical module made to store, sort, and apply standard machine parts."
icon_state = "RPED"
inhand_icon_state = "RPED"
+ worn_icon_state = "RPED"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
w_class = WEIGHT_CLASS_HUGE
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 08c57c986d0..7e71f86788b 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -131,6 +131,7 @@
display_name = "Data Theory"
description = "Big Data, in space!"
prereq_ids = list("base")
+ design_ids = list("bounty_pad","bounty_pad_control")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
@@ -301,6 +302,7 @@
display_name = "Neural Programming"
description = "Study into networks of processing units that mimic our brains."
prereq_ids = list("biotech", "datatheory")
+ design_ids = list("skill_station")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
@@ -1078,23 +1080,12 @@
hidden = TRUE
experimental = TRUE
-/datum/techweb_node/rolling_table
- id = "rolling_table"
- display_name = "Advanced Wheel Applications"
- description = "Adding wheels to things can lead to extremely beneficial outcomes."
+/datum/techweb_node/extreme_office
+ id = "extreme_office"
+ display_name = "Advanced Office Applications"
+ description = "Some of our smartest lab guys got together on a Friday and improved our office efficiency by 350%. Here's how."
prereq_ids = list("base")
- design_ids = list("rolling_table")
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
- export_price = 2500
- hidden = TRUE
- experimental = TRUE
-
-/datum/techweb_node/mauna_mug
- id = "mauna_mug"
- display_name = "Mauna Mug"
- description = "A bored scientist was thinking to himself for very long...and then realized his coffee got cold! He made this invention to solve this extreme problem."
- prereq_ids = list("base")
- design_ids = list("mauna_mug")
+ design_ids = list("rolling_table", "mauna_mug")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 2500
hidden = TRUE
diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm
index ac3a051031b..4439a19b448 100644
--- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm
@@ -223,7 +223,7 @@
duration = -1
alert_type = null
-datum/status_effect/rebreathing/tick()
+/datum/status_effect/rebreathing/tick()
owner.adjustOxyLoss(-6, 0) //Just a bit more than normal breathing.
///////////////////////////////////////////////////////
@@ -505,7 +505,7 @@ datum/status_effect/rebreathing/tick()
ADD_TRAIT(owner, TRAIT_NOSLIPWATER, "slimestatus")
return ..()
-datum/status_effect/stabilized/blue/on_remove()
+/datum/status_effect/stabilized/blue/on_remove()
REMOVE_TRAIT(owner, TRAIT_NOSLIPWATER, "slimestatus")
/datum/status_effect/stabilized/metal
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 5cc8fbe33b1..717409bc8f7 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -2,6 +2,7 @@
#define ENGINES_START_TIME 100
#define ENGINES_STARTED (SSshuttle.emergency.mode == SHUTTLE_IGNITING)
#define IS_DOCKED (SSshuttle.emergency.mode == SHUTTLE_DOCKED || (ENGINES_STARTED))
+#define SHUTTLE_CONSOLE_ACTION_DELAY (5 SECONDS)
/obj/machinery/computer/emergency_shuttle
name = "emergency shuttle console"
@@ -11,6 +12,7 @@
var/auth_need = 3
var/list/authorized = list()
+ var/list/acted_recently = list()
/obj/machinery/computer/emergency_shuttle/attackby(obj/item/I, mob/user,params)
if(istype(I, /obj/item/card/id))
@@ -21,13 +23,12 @@
return GLOB.human_adjacent_state
/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, datum/tgui/ui)
-
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "EmergencyShuttleConsole", name)
ui.open()
-/obj/machinery/computer/emergency_shuttle/ui_data()
+/obj/machinery/computer/emergency_shuttle/ui_data(user)
var/list/data = list()
data["timer_str"] = SSshuttle.emergency.getTimerStr()
@@ -45,7 +46,7 @@
A += list(list("name" = name, "job" = job))
data["authorizations"] = A
- data["enabled"] = (IS_DOCKED && !ENGINES_STARTED)
+ data["enabled"] = (IS_DOCKED && !ENGINES_STARTED) && !(user in acted_recently)
data["emagged"] = obj_flags & EMAGGED ? 1 : 0
return data
@@ -70,7 +71,11 @@
to_chat(user, "The access level of your card is not high enough.")
return
+ if (user in acted_recently)
+ return
+
var/old_len = authorized.len
+ addtimer(CALLBACK(src, .proc/clear_recent_action, user), SHUTTLE_CONSOLE_ACTION_DELAY)
switch(action)
if("authorize")
@@ -95,6 +100,9 @@
if(repeal)
minor_announce("Early launch authorization revoked, [remaining] authorizations needed")
+ acted_recently += user
+ ui_interact(user)
+
/obj/machinery/computer/emergency_shuttle/proc/authorize(mob/user, source)
var/obj/item/card/id/ID = user.get_idcard(TRUE)
@@ -113,6 +121,11 @@
. = TRUE
process()
+/obj/machinery/computer/emergency_shuttle/proc/clear_recent_action(mob/user)
+ acted_recently -= user
+ if (!QDELETED(user))
+ ui_interact(user)
+
/obj/machinery/computer/emergency_shuttle/process()
// Launch check is in process in case auth_need changes for some reason
// probably external.
@@ -595,3 +608,4 @@
#undef ENGINES_START_TIME
#undef ENGINES_STARTED
#undef IS_DOCKED
+#undef SHUTTLE_CONSOLE_ACTION_DELAY
diff --git a/code/modules/shuttle/shuttle_rotate.dm b/code/modules/shuttle/shuttle_rotate.dm
index 5b5f325d9f4..fbdfd91dbf2 100644
--- a/code/modules/shuttle/shuttle_rotate.dm
+++ b/code/modules/shuttle/shuttle_rotate.dm
@@ -12,8 +12,8 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
setDir(angle2dir(rotation+dir2angle(dir)))
//resmooth if need be.
- if(smooth && (params & ROTATE_SMOOTH))
- queue_smooth(src)
+ if(smoothing_flags && (params & ROTATE_SMOOTH))
+ QUEUE_SMOOTH(src)
//rotate the pixel offsets too.
if((pixel_x || pixel_y) && (params & ROTATE_OFFSET))
diff --git a/code/modules/shuttle/spaceship_navigation_beacon.dm b/code/modules/shuttle/spaceship_navigation_beacon.dm
index ec6efb1e6b4..e5b4cf4ea79 100644
--- a/code/modules/shuttle/spaceship_navigation_beacon.dm
+++ b/code/modules/shuttle/spaceship_navigation_beacon.dm
@@ -21,7 +21,7 @@
. = ..()
SSshuttle.beacons |= src
-obj/machinery/spaceship_navigation_beacon/emp_act()
+/obj/machinery/spaceship_navigation_beacon/emp_act()
locked = TRUE
/obj/machinery/spaceship_navigation_beacon/Destroy()
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index 144f38ec681..006ce3abaa4 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -331,7 +331,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/obj/effect/overlay/spell = new /obj/effect/overlay(location)
spell.icon = overlay_icon
spell.icon_state = overlay_icon_state
- spell.anchored = TRUE
+ spell.set_anchored(TRUE)
spell.density = FALSE
QDEL_IN(spell, overlay_lifespan)
diff --git a/code/modules/station_goals/shield.dm b/code/modules/station_goals/shield.dm
index e9b8d039cc3..34f006110f4 100644
--- a/code/modules/station_goals/shield.dm
+++ b/code/modules/station_goals/shield.dm
@@ -102,6 +102,19 @@
/obj/machinery/satellite/interact(mob/user)
toggle(user)
+/obj/machinery/satellite/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return //no need to process if we didn't change anything.
+ active = anchorvalue
+ if(anchorvalue)
+ begin_processing()
+ animate(src, pixel_y = 2, time = 10, loop = -1)
+ else
+ end_processing()
+ animate(src, pixel_y = 0, time = 10)
+ update_icon()
+
/obj/machinery/satellite/proc/toggle(mob/user)
if(!active && !isinspace())
if(user)
@@ -109,16 +122,7 @@
return FALSE
if(user)
to_chat(user, "You [active ? "deactivate": "activate"] [src].")
- active = !active
- if(active)
- begin_processing()
- animate(src, pixel_y = 2, time = 10, loop = -1)
- anchored = TRUE
- else
- end_processing()
- animate(src, pixel_y = 0, time = 10)
- anchored = FALSE
- update_icon()
+ set_anchored(!anchored)
/obj/machinery/satellite/update_icon_state()
icon_state = active ? "sat_active" : "sat_inactive"
diff --git a/code/modules/surgery/advanced/wingreconstruction.dm b/code/modules/surgery/advanced/wingreconstruction.dm
index fa16e902adc..ae2df9ea7f8 100644
--- a/code/modules/surgery/advanced/wingreconstruction.dm
+++ b/code/modules/surgery/advanced/wingreconstruction.dm
@@ -1,6 +1,6 @@
/datum/surgery/advanced/wing_reconstruction
name = "Wing Reconstruction"
- desc = "An experimental surgical procedure that reconstructs the damaged wings of moth people. Requires Instabitaluri."
+ desc = "An experimental surgical procedure that reconstructs the damaged wings of moth people. Requires Synthflesh."
steps = list(/datum/surgery_step/incise,
/datum/surgery_step/retract_skin,
/datum/surgery_step/clamp_bleeders,
@@ -15,7 +15,7 @@
name = "start wing reconstruction"
implements = list(TOOL_HEMOSTAT = 85, TOOL_SCREWDRIVER = 35, /obj/item/pen = 15)
time = 200
- chems_needed = list(/datum/reagent/medicine/c2/instabitaluri)
+ chems_needed = list(/datum/reagent/medicine/c2/synthflesh)
require_all_chems = FALSE
/datum/surgery_step/wing_reconstruction/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index 55ca39ec0a6..0adebde80d8 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -81,12 +81,12 @@
/// A hat won't cover your face, but a shirt covering your chest will cover your... you know, chest
var/scars_covered_by_clothes = TRUE
- /// Descriptions for the locations on the limb for scars to be assigned, just cosmetic
- var/list/specific_locations = list("general area")
/// So we know if we need to scream if this limb hits max damage
var/last_maxed
/// How much generic bleedstacks we have on this bodypart
var/generic_bleedstacks
+ /// If we have a gauze wrapping currently applied (not including splints)
+ var/obj/item/stack/current_gauze
/obj/item/bodypart/examine(mob/user)
@@ -149,9 +149,23 @@
var/turf/T = get_turf(src)
if(status != BODYPART_ROBOTIC)
playsound(T, 'sound/misc/splort.ogg', 50, TRUE, -1)
+ if(current_gauze)
+ QDEL_NULL(current_gauze)
+ for(var/obj/item/organ/drop_organ in get_organs())
+ drop_organ.transfer_to_limb(src, owner)
for(var/obj/item/I in src)
I.forceMove(T)
+///since organs aren't actually stored in the bodypart themselves while attached to a person, we have to query the owner for what we should have
+/obj/item/bodypart/proc/get_organs()
+ if(!owner)
+ return
+ . = list()
+ for(var/i in owner.internal_organs) //internal organs inside the dismembered limb are dropped.
+ var/obj/item/organ/organ_check = i
+ if(check_zone(organ_check.zone) == body_zone)
+ . += organ_check
+
/obj/item/bodypart/proc/consider_processing()
if(stamina_dam > DAMAGE_PRECISION)
. = TRUE
@@ -169,7 +183,7 @@
//Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all.
//Damage will not exceed max_damage using this proc
//Cannot apply negative damage
-/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, stamina = 0, blocked = 0, updating_health = TRUE, required_status = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE) // maybe separate BRUTE_SHARP and BRUTE_OTHER eventually somehow hmm
+/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, stamina = 0, blocked = 0, updating_health = TRUE, required_status = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) // maybe separate BRUTE_SHARP and BRUTE_OTHER eventually somehow hmm
var/hit_percent = (100-blocked)/100
if((!brute && !burn && !stamina) || hit_percent <= 0)
return FALSE
@@ -197,16 +211,64 @@
if(ALIEN_BODYPART,LARVA_BODYPART) //aliens take double burn //nothing can burn with so much snowflake code around
burn *= 2
- var/wounding_type = (brute > burn ? WOUND_BRUTE : WOUND_BURN)
+ /*
+ // START WOUND HANDLING
+ */
+
+ // what kind of wounds we're gonna roll for, take the greater between brute and burn, then if it's brute, we subdivide based on sharpness
+ var/wounding_type = (brute > burn ? WOUND_BLUNT : WOUND_BURN)
var/wounding_dmg = max(brute, burn)
- if(wounding_type == WOUND_BRUTE && sharpness)
- wounding_type = WOUND_SHARP
- // i know this is effectively the same check as above but i don't know if those can null the damage by rounding and want to be safe
- if(owner && wounding_dmg > 4 && wound_bonus != CANT_WOUND)
- // if you want to make tox wounds or some other type, this will need to be expanded and made more modular
- // handle all our wounding stuff
+
+ var/mangled_state = get_mangled_state()
+ var/bio_state = owner.get_biological_state()
+ var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
+
+ if(wounding_type == WOUND_BLUNT)
+ if(sharpness == SHARP_EDGED)
+ wounding_type = WOUND_SLASH
+ else if(sharpness == SHARP_POINTY)
+ wounding_type = WOUND_PIERCE
+
+ //Handling for bone only/flesh only(none right now)/flesh and bone targets
+ switch(bio_state)
+ // if we're bone only, all cutting attacks go straight to the bone
+ if(BIO_JUST_BONE)
+ if(wounding_type == WOUND_SLASH)
+ wounding_type = WOUND_BLUNT
+ wounding_dmg *= (easy_dismember ? 1 : 0.5)
+ else if(wounding_type == WOUND_PIERCE)
+ wounding_type = WOUND_BLUNT
+ wounding_dmg *= (easy_dismember ? 1 : 0.75)
+ if((mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
+ return
+ // note that there's no handling for BIO_JUST_FLESH since we don't have any that are that right now (slimepeople maybe someday)
+ // standard humanoids
+ if(BIO_FLESH_BONE)
+ // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
+ // So a big sharp weapon is still all you need to destroy a limb
+ if(mangled_state == BODYPART_MANGLED_FLESH && sharpness)
+ playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
+ if(wounding_type == WOUND_SLASH && !easy_dismember)
+ wounding_dmg *= 0.5 // edged weapons pass along 50% of their wounding damage to the bone since the power is spread out over a larger area
+ if(wounding_type == WOUND_PIERCE && !easy_dismember)
+ wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
+ wounding_type = WOUND_BLUNT
+ else if(mangled_state == BODYPART_MANGLED_BOTH && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
+ return
+
+ // now we have our wounding_type and are ready to carry on with wounds and dealing the actual damage
+ if(owner && wounding_dmg >= WOUND_MINIMUM_DAMAGE && wound_bonus != CANT_WOUND)
check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)
+ for(var/i in wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.receive_damage(wounding_type, wounding_dmg, wound_bonus)
+
+ /*
+ // END WOUND HANDLING
+ */
+
+ //back to our regularly scheduled program, we now actually apply damage if there's room below limb damage cap
var/can_inflict = max_damage - get_damage()
var/total_damage = brute + burn
if(total_damage > can_inflict && total_damage > 0) // TODO: the second part of this check should be removed once disabling is all done
@@ -219,10 +281,6 @@
brute_dam += brute
burn_dam += burn
- for(var/i in wounds)
- var/datum/wound/W = i
- W.receive_damage(wounding_type, wounding_dmg, wound_bonus)
-
//We've dealt the physical damages, if there's room lets apply the stamina damage.
stamina_dam += round(clamp(stamina, 0, max_stamina_damage - stamina_dam), DAMAGE_PRECISION)
@@ -236,14 +294,58 @@
update_disabled()
return update_bodypart_damage_state() || .
+/// Allows us to roll for and apply a wound without actually dealing damage. Used for aggregate wounding power with pellet clouds
+/obj/item/bodypart/proc/painless_wound_roll(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=SHARP_NONE)
+ if(!owner || phantom_wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND)
+ return
+
+ var/mangled_state = get_mangled_state()
+ var/bio_state = owner.get_biological_state()
+ var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
+
+ if(wounding_type == WOUND_BLUNT)
+ if(sharpness == SHARP_EDGED)
+ wounding_type = WOUND_SLASH
+ else if(sharpness == SHARP_POINTY)
+ wounding_type = WOUND_PIERCE
+
+ //Handling for bone only/flesh only(none right now)/flesh and bone targets
+ switch(bio_state)
+ // if we're bone only, all cutting attacks go straight to the bone
+ if(BIO_JUST_BONE)
+ if(wounding_type == WOUND_SLASH)
+ wounding_type = WOUND_BLUNT
+ phantom_wounding_dmg *= (easy_dismember ? 1 : 0.5)
+ else if(wounding_type == WOUND_PIERCE)
+ wounding_type = WOUND_BLUNT
+ phantom_wounding_dmg *= (easy_dismember ? 1 : 0.75)
+ if((mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
+ return
+ // note that there's no handling for BIO_JUST_FLESH since we don't have any that are that right now (slimepeople maybe someday)
+ // standard humanoids
+ if(BIO_FLESH_BONE)
+ // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
+ // So a big sharp weapon is still all you need to destroy a limb
+ if(mangled_state == BODYPART_MANGLED_FLESH && sharpness)
+ playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
+ if(wounding_type == WOUND_SLASH && !easy_dismember)
+ phantom_wounding_dmg *= 0.5 // edged weapons pass along 50% of their wounding damage to the bone since the power is spread out over a larger area
+ if(wounding_type == WOUND_PIERCE && !easy_dismember)
+ phantom_wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
+ wounding_type = WOUND_BLUNT
+ else if(mangled_state == BODYPART_MANGLED_BOTH && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
+ return
+
+ check_wounding(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)
+
/**
* check_wounding() is where we handle rolling for, selecting, and applying a wound if we meet the criteria
*
- * We generate a "score" for how woundable the attack was based on the damage and other factors discussed in [check_wounding_mods()], then go down the list from most severe to least severe wounds in that category.
+ * We generate a "score" for how woundable the attack was based on the damage and other factors discussed in [/obj/item/bodypart/proc/check_wounding_mods], then go down the list from most severe to least severe wounds in that category.
* We can promote a wound from a lesser to a higher severity this way, but we give up if we have a wound of the given type and fail to roll a higher severity, so no sidegrades/downgrades
*
* Arguments:
- * * woundtype- Either WOUND_SHARP, WOUND_BRUTE, or WOUND_BURN based on the attack type.
+ * * woundtype- Either WOUND_BLUNT, WOUND_SLASH, WOUND_PIERCE, or WOUND_BURN based on the attack type.
* * damage- How much damage is tied to this attack, since wounding potential scales with damage in an attack (see: WOUND_DAMAGE_EXPONENT)
* * wound_bonus- The wound_bonus of an attack
* * bare_wound_bonus- The bare_wound_bonus of an attack
@@ -252,21 +354,29 @@
// actually roll wounds if applicable
if(HAS_TRAIT(owner, TRAIT_EASYLIMBDISABLE))
damage *= 1.5
+ else
+ damage = min(damage, WOUND_MAX_CONSIDERED_DAMAGE)
if(HAS_TRAIT(owner,TRAIT_HARDLIMBDISABLE))
- damage *= 0.5
+ damage *= 0.8
+
+ if(HAS_TRAIT(owner, TRAIT_EASYDISMEMBER))
+ damage *= 1.25
var/base_roll = rand(1, round(damage ** WOUND_DAMAGE_EXPONENT))
var/injury_roll = base_roll
injury_roll += check_woundings_mods(woundtype, damage, wound_bonus, bare_wound_bonus)
- var/list/wounds_checking
+ var/list/wounds_checking = GLOB.global_wound_types[woundtype]
- switch(woundtype)
- if(WOUND_SHARP)
- wounds_checking = WOUND_LIST_CUT
- if(WOUND_BRUTE)
- wounds_checking = WOUND_LIST_BONE
- if(WOUND_BURN)
- wounds_checking = WOUND_LIST_BURN
+ // quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_woundings_mods() somehow
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human_wearer = owner
+ var/list/clothing = human_wearer.clothingonpart(src)
+ for(var/i in clothing)
+ var/obj/item/clothing/clothes_check = i
+ // unlike normal armor checks, we tabluate these piece-by-piece manually so we can also pass on appropriate damage the clothing's limbs if necessary
+ if(clothes_check.armor.getRating("wound"))
+ bare_wound_bonus = 0
+ break
//cycle through the wounds of the relevant category from the most severe down
for(var/PW in wounds_checking)
@@ -281,21 +391,22 @@
replaced_wound = existing_wound
if(initial(possible_wound.threshold_minimum) < injury_roll)
+ var/datum/wound/new_wound
if(replaced_wound)
- var/datum/wound/new_wound = replaced_wound.replace_wound(possible_wound)
- log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll)
+ new_wound = replaced_wound.replace_wound(possible_wound)
+ log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned
else
- var/datum/wound/new_wound = new possible_wound
+ new_wound = new possible_wound
new_wound.apply_wound(src)
log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll)
- return
+ return new_wound
// try forcing a specific wound, but only if there isn't already a wound of that severity or greater for that type on this bodypart
/obj/item/bodypart/proc/force_wound_upwards(specific_woundtype, smited = FALSE)
var/datum/wound/potential_wound = specific_woundtype
for(var/i in wounds)
var/datum/wound/existing_wound = i
- if(existing_wound.type in (initial(potential_wound.wound_type)))
+ if(existing_wound.wound_type == initial(potential_wound.wound_type))
if(existing_wound.severity < initial(potential_wound.severity)) // we only try if the existing one is inferior to the one we're trying to force
existing_wound.replace_wound(potential_wound, smited)
return
@@ -303,12 +414,20 @@
var/datum/wound/new_wound = new potential_wound
new_wound.apply_wound(src, smited = smited)
+/**
+ * check_wounding_mods() is where we handle the various modifiers of a wound roll
+ *
+ * A short list of things we consider: any armor a human target may be wearing, and if they have no wound armor on the limb, if we have a bare_wound_bonus to apply, plus the plain wound_bonus
+ * We also flick through all of the wounds we currently have on this limb and add their threshold penalties, so that having lots of bad wounds makes you more liable to get hurt worse
+ * Lastly, we add the inherent wound_resistance variable the bodypart has (heads and chests are slightly harder to wound), and a small bonus if the limb is already disabled
+ *
+ * Arguments:
+ * * It's the same ones on [receive_damage]
+ */
/obj/item/bodypart/proc/check_woundings_mods(wounding_type, damage, wound_bonus, bare_wound_bonus)
var/armor_ablation = 0
var/injury_mod = 0
- //var/bwb = 0
-
if(owner && ishuman(owner))
var/mob/living/carbon/human/H = owner
var/list/clothing = H.clothingonpart(src)
@@ -316,7 +435,7 @@
var/obj/item/clothing/C = c
// unlike normal armor checks, we tabluate these piece-by-piece manually so we can also pass on appropriate damage the clothing's limbs if necessary
armor_ablation += C.armor.getRating("wound")
- if(wounding_type == WOUND_SHARP)
+ if(wounding_type == WOUND_SLASH)
C.take_damage_zone(body_zone, damage, BRUTE, armour_penetration)
else if(wounding_type == WOUND_BURN && damage >= 10) // lazy way to block freezing from shredding clothes without adding another var onto apply_damage()
C.take_damage_zone(body_zone, damage, BURN, armour_penetration)
@@ -332,7 +451,7 @@
injury_mod += W.threshold_penalty
var/part_mod = -wound_resistance
- if(is_disabled())
+ if(get_damage(TRUE) >= max_damage)
part_mod += disabled_wound_penalty
injury_mod += part_mod
@@ -382,12 +501,11 @@
if(can_dismember() && !HAS_TRAIT(owner, TRAIT_NOLIMBDISABLE))
. = disabled //inertia, to avoid limbs healing 0.1 damage and being re-enabled
- // TODO: figure if i'm keeping disabling to broken bones only
- if((get_damage(TRUE) >= max_damage) || (HAS_TRAIT(owner, TRAIT_EASYLIMBDISABLE) && (get_damage(TRUE) >= (max_damage * 0.6)))) //Easy limb disable disables the limb at 40% health instead of 0%
+ if(get_damage(TRUE) >= max_damage * (HAS_TRAIT(owner, TRAIT_EASYLIMBDISABLE) ? 0.6 : 1)) //Easy limb disable disables the limb at 40% health instead of 0%
if(!last_maxed)
owner.emote("scream")
last_maxed = TRUE
- if(!is_organic_limb())
+ if(!is_organic_limb() || stamina_dam >= max_damage)
return BODYPART_DISABLED_DAMAGE
else if(disabled && (get_damage(TRUE) <= (max_damage * 0.8))) // reenabled at 80% now instead of 50% as of wounds update
last_maxed = FALSE
@@ -603,18 +721,29 @@
if(isnull(wounds))
return
- for(var/thing in wounds)
- var/datum/wound/W = thing
- if(istype(W, checking_type))
- return W
+ for(var/i in wounds)
+ if(istype(i, checking_type))
+ return i
-/// very rough start for updating efficiency and other stats on a body part whenever a wound is gained/lost
-/obj/item/bodypart/proc/update_wounds()
+/**
+ * update_wounds() is called whenever a wound is gained or lost on this bodypart, as well as if there's a change of some kind on a bone wound possibly changing disabled status
+ *
+ * Covers tabulating the damage multipliers we have from wounds (burn specifically), as well as deleting our gauze wrapping if we don't have any wounds that can use bandaging
+ *
+ * Arguments:
+ * * replaced- If true, this is being called from the remove_wound() of a wound that's being replaced, so the bandage that already existed is still relevant, but the new wound hasn't been added yet
+ */
+/obj/item/bodypart/proc/update_wounds(replaced = FALSE)
var/dam_mul = 1 //initial(wound_damage_multiplier)
- // we can only have one wound per type, but remember there's multiple types
- for(var/datum/wound/W in wounds)
- dam_mul *= W.damage_mulitplier_penalty
+ // we can (normally) only have one wound per type, but remember there's multiple types (smites like :B:loodless can generate multiple cuts on a limb)
+ for(var/i in wounds)
+ var/datum/wound/iter_wound = i
+ dam_mul *= iter_wound.damage_mulitplier_penalty
+
+ if(!LAZYLEN(wounds) && current_gauze && !replaced)
+ owner.visible_message("\The [current_gauze] on [owner]'s [name] fall away.", "The [current_gauze] on your [name] fall away.")
+ QDEL_NULL(current_gauze)
wound_damage_multiplier = dam_mul
update_disabled()
@@ -627,9 +756,6 @@
if(generic_bleedstacks > 0)
bleed_rate++
- if(brute_dam >= 40)
- bleed_rate += (brute_dam * 0.008)
-
//We want an accurate reading of .len
listclearnulls(embedded_objects)
for(var/obj/item/embeddies in embedded_objects)
@@ -640,4 +766,40 @@
var/datum/wound/W = thing
bleed_rate += W.blood_flow
+ if(owner.mobility_flags & ~MOBILITY_STAND)
+ bleed_rate *= 0.75
return bleed_rate
+
+/**
+ * apply_gauze() is used to- well, apply gauze to a bodypart
+ *
+ * As of the Wounds 2 PR, all bleeding is now bodypart based rather than the old bleedstacks system, and 90% of standard bleeding comes from flesh wounds (the exception is embedded weapons).
+ * The same way bleeding is totaled up by bodyparts, gauze now applies to all wounds on the same part. Thus, having a slash wound, a pierce wound, and a broken bone wound would have the gauze
+ * applying blood staunching to the first two wounds, while also acting as a sling for the third one. Once enough blood has been absorbed or all wounds with the ACCEPTS_GAUZE flag have been cleared,
+ * the gauze falls off.
+ *
+ * Arguments:
+ * * gauze- Just the gauze stack we're taking a sheet from to apply here
+ */
+/obj/item/bodypart/proc/apply_gauze(obj/item/stack/gauze)
+ if(!istype(gauze) || !gauze.absorption_capacity)
+ return
+ QDEL_NULL(current_gauze)
+ current_gauze = new gauze.type(src, 1)
+ gauze.use(1)
+
+/**
+ * seep_gauze() is for when a gauze wrapping absorbs blood or pus from wounds, lowering its absorption capacity.
+ *
+ * The passed amount of seepage is deducted from the bandage's absorption capacity, and if we reach a negative absorption capacity, the bandages fall off and we're left with nothing.
+ *
+ * Arguments:
+ * * seep_amt - How much absorption capacity we're removing from our current bandages (think, how much blood or pus are we soaking up this tick?)
+ */
+/obj/item/bodypart/proc/seep_gauze(seep_amt = 0)
+ if(!current_gauze)
+ return
+ current_gauze.absorption_capacity -= seep_amt
+ if(current_gauze.absorption_capacity < 0)
+ owner.visible_message("\The [current_gauze] on [owner]'s [name] fall away in rags.", "\The [current_gauze] on your [name] fall away in rags.", vision_distance=COMBAT_MESSAGE_RANGE)
+ QDEL_NULL(current_gauze)
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index 48312107dad..f02c7a86a36 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -4,7 +4,7 @@
return TRUE
//Dismember a limb
-/obj/item/bodypart/proc/dismember(dam_type = BRUTE)
+/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE)
if(!owner)
return FALSE
var/mob/living/carbon/C = owner
@@ -17,7 +17,8 @@
var/obj/item/bodypart/affecting = C.get_bodypart(BODY_ZONE_CHEST)
affecting.receive_damage(clamp(brute_dam/2 * affecting.body_damage_coeff, 15, 50), clamp(burn_dam/2 * affecting.body_damage_coeff, 0, 50), wound_bonus=CANT_WOUND) //Damage the chest based on limb's existing damage
- C.visible_message("[C]'s [src.name] is violently dismembered!")
+ if(!silent)
+ C.visible_message("[C]'s [name] is violently dismembered!")
C.emote("scream")
playsound(get_turf(C), 'sound/effects/dismember.ogg', 80, TRUE)
SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "dismembered", /datum/mood_event/dismembered)
@@ -34,6 +35,7 @@
burn()
return TRUE
add_mob_blood(C)
+ C.bleed(rand(20, 40))
var/direction = pick(GLOB.cardinals)
var/t_range = rand(2,max(throw_range/2, 2))
var/turf/target_turf = get_turf(src)
@@ -154,7 +156,52 @@
forceMove(Tsec)
+/**
+ * get_mangled_state() is relevant for flesh and bone bodyparts, and returns whether this bodypart has mangled skin, mangled bone, or both (or neither i guess)
+ *
+ * Dismemberment for flesh and bone requires the victim to have the skin on their bodypart destroyed (either a critical cut or piercing wound), and at least a hairline fracture
+ * (severe bone), at which point we can start rolling for dismembering. The attack must also deal at least 10 damage, and must be a brute attack of some kind (sorry for now, cakehat, maybe later)
+ *
+ * Returns: BODYPART_MANGLED_NONE if we're fine, BODYPART_MANGLED_FLESH if our skin is broken, BODYPART_MANGLED_BONE if our bone is broken, or BODYPART_MANGLED_BOTH if both are broken and we're up for dismembering
+ */
+/obj/item/bodypart/proc/get_mangled_state()
+ . = BODYPART_MANGLED_NONE
+ for(var/i in wounds)
+ var/datum/wound/iter_wound = i
+ if((iter_wound.wound_flags & MANGLES_BONE))
+ . |= BODYPART_MANGLED_BONE
+ if((iter_wound.wound_flags & MANGLES_FLESH))
+ . |= BODYPART_MANGLED_FLESH
+
+/**
+ * try_dismember() is used, once we've confirmed that a flesh and bone bodypart has both the skin and bone mangled, to actually roll for it
+ *
+ * Mangling is described in the above proc, [/obj/item/bodypart/proc/get_mangled_state()]. This simply makes the roll for whether we actually dismember or not
+ * using how damaged the limb already is, and how much damage this blow was for. If we have a critical bone wound instead of just a severe, we add +10% to the roll.
+ * Lastly, we choose which kind of dismember we want based on the wounding type we hit with. Note we don't care about all the normal mods or armor for this
+ *
+ * Arguments:
+ * * wounding_type: Either WOUND_BLUNT, WOUND_SLASH, or WOUND_PIERCE, basically only matters for the dismember message
+ * * wounding_dmg: The damage of the strike that prompted this roll, higher damage = higher chance
+ * * wound_bonus: Not actually used right now, but maybe someday
+ * * bare_wound_bonus: ditto above
+ */
+/obj/item/bodypart/proc/try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)
+ if(wounding_dmg < DISMEMBER_MINIMUM_DAMAGE)
+ return
+
+ var/base_chance = wounding_dmg + (get_damage() / max_damage * 50) // how much damage we dealt with this blow, + 50% of the damage percentage we already had on this bodypart
+ if(locate(/datum/wound/blunt/critical) in wounds) // we only require a severe bone break, but if there's a critical bone break, we'll add 10% more
+ base_chance += 10
+
+ if(!prob(base_chance))
+ return
+
+ var/datum/wound/loss/dismembering = new
+ dismembering.apply_dismember(src, wounding_type)
+
+ return TRUE
//when a limb is dropped, the internal organs are removed from the mob and put into the limb
/obj/item/organ/proc/transfer_to_limb(obj/item/bodypart/LB, mob/living/carbon/C)
@@ -414,4 +461,7 @@
if(!L.attach_limb(src, 1))
qdel(L)
return FALSE
+ var/datum/scar/scaries = new
+ var/datum/wound/loss/phantom_loss = new // stolen valor, really
+ scaries.generate(L, phantom_loss)
return TRUE
diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm
index 9abbf3441b6..61ddecefb1f 100644
--- a/code/modules/surgery/bodyparts/head.dm
+++ b/code/modules/surgery/bodyparts/head.dm
@@ -14,7 +14,6 @@
stam_damage_coeff = 1
max_stamina_damage = 100
wound_resistance = 10
- specific_locations = list("left eyebrow", "cheekbone", "neck", "throat", "jawline", "entire face")
scars_covered_by_clothes = FALSE
var/mob/living/brain/brainmob = null //The current occupant.
@@ -94,7 +93,7 @@
/obj/item/bodypart/head/can_dismember(obj/item/I)
- if(!((owner.stat == DEAD) || owner.InFullCritical()))
+ if(owner && !((owner.stat == DEAD) || owner.InFullCritical()))
return FALSE
return ..()
diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm
index bd4abaa7650..3dea81eff1d 100644
--- a/code/modules/surgery/bodyparts/parts.dm
+++ b/code/modules/surgery/bodyparts/parts.dm
@@ -11,11 +11,10 @@
stam_damage_coeff = 1
max_stamina_damage = 120
var/obj/item/cavity_item
- specific_locations = list("upper chest", "lower abdomen", "midsection", "collarbone", "lower back")
wound_resistance = 10
/obj/item/bodypart/chest/can_dismember(obj/item/I)
- if(!((owner.stat == DEAD) || owner.InFullCritical()))
+ if(!((owner.stat == DEAD) || owner.InFullCritical()) || !get_organs())
return FALSE
return ..()
@@ -72,7 +71,6 @@
held_index = 1
px_x = -6
px_y = 0
- specific_locations = list("outer left forearm", "inner left wrist", "left elbow", "left bicep", "left shoulder")
/obj/item/bodypart/l_arm/is_disabled()
if(HAS_TRAIT(owner, TRAIT_PARALYSIS_L_ARM))
@@ -138,7 +136,6 @@
px_x = 6
px_y = 0
max_stamina_damage = 50
- specific_locations = list("outer right forearm", "inner right wrist", "right elbow", "right bicep", "right shoulder")
/obj/item/bodypart/r_arm/is_disabled()
if(HAS_TRAIT(owner, TRAIT_PARALYSIS_R_ARM))
@@ -201,7 +198,6 @@
px_x = -2
px_y = 12
max_stamina_damage = 50
- specific_locations = list("inner left thigh", "outer left calf", "outer left hip", " left kneecap", "lower left shin")
/obj/item/bodypart/l_leg/is_disabled()
if(HAS_TRAIT(owner, TRAIT_PARALYSIS_L_LEG))
@@ -261,7 +257,6 @@
px_x = 2
px_y = 12
max_stamina_damage = 50
- specific_locations = list("inner right thigh", "outer right calf", "outer right hip", "right kneecap", "lower right shin")
/obj/item/bodypart/r_leg/is_disabled()
if(HAS_TRAIT(owner, TRAIT_PARALYSIS_R_LEG))
diff --git a/code/modules/surgery/bone_mending.dm b/code/modules/surgery/bone_mending.dm
index 81d9fa8d97d..0c0083575bb 100644
--- a/code/modules/surgery/bone_mending.dm
+++ b/code/modules/surgery/bone_mending.dm
@@ -8,7 +8,7 @@
target_mobtypes = list(/mob/living/carbon/human)
possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
requires_real_bodypart = TRUE
- targetable_wound = /datum/wound/brute/bone/severe
+ targetable_wound = /datum/wound/blunt/severe
/datum/surgery/repair_bone_hairline/can_start(mob/living/user, mob/living/carbon/target)
if(..())
@@ -23,7 +23,7 @@
target_mobtypes = list(/mob/living/carbon/human)
possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
requires_real_bodypart = TRUE
- targetable_wound = /datum/wound/brute/bone/critical
+ targetable_wound = /datum/wound/blunt/critical
/datum/surgery/repair_bone_compound/can_start(mob/living/user, mob/living/carbon/target)
if(..())
diff --git a/code/modules/surgery/burn_dressing.dm b/code/modules/surgery/burn_dressing.dm
index 1688f7b5d17..8bfa52d2454 100644
--- a/code/modules/surgery/burn_dressing.dm
+++ b/code/modules/surgery/burn_dressing.dm
@@ -94,7 +94,8 @@
log_combat(user, target, "dressed burns in", addition="INTENT: [uppertext(user.a_intent)]")
burn_wound.sanitization += 3
burn_wound.flesh_healing += 5
- burn_wound.force_bandage(tool)
+ var/obj/item/bodypart/the_part = target.get_bodypart(target_zone)
+ the_part.apply_gauze(tool)
else
to_chat(user, "[target] has no burns there!")
return ..()
diff --git a/code/modules/surgery/healing.dm b/code/modules/surgery/healing.dm
index d5f6d66b75d..7801071a186 100644
--- a/code/modules/surgery/healing.dm
+++ b/code/modules/surgery/healing.dm
@@ -6,7 +6,7 @@
/datum/surgery_step/heal,
/datum/surgery_step/close)
- target_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
+ target_mobtypes = list(/mob/living)
possible_locs = list(BODY_ZONE_CHEST)
requires_bodypart_type = FALSE
replaced_by = /datum/surgery
@@ -14,6 +14,15 @@
var/healing_step_type
var/antispam = FALSE
+/datum/surgery/healing/can_start(mob/user, mob/living/patient)
+ . = ..()
+ if(isanimal(patient))
+ var/mob/living/simple_animal/critter = patient
+ if(!critter.healable)
+ return FALSE
+ if(!(patient.mob_biotypes & (MOB_ORGANIC|MOB_HUMANOID)))
+ return FALSE
+
/datum/surgery/healing/New(surgery_target, surgery_location, surgery_bodypart)
..()
if(healing_step_type)
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index 2c22fc5bdb0..f8c25d5577d 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -126,6 +126,48 @@
/obj/item/organ/proc/OnEatFrom(eater, feeder)
useable = FALSE //You can't use it anymore after eating it you spaztic
+/*
+ * On accidental consumption, cause organ damage and check if they like eating organs
+ */
+/obj/item/organ/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ if(organ_flags & ORGAN_SYNTHETIC)
+ return ..()
+
+ if(organ_flags & ORGAN_FROZEN)
+ return TRUE
+
+ applyOrganDamage(25)
+ OnEatFrom(M, user)
+ if(istype(src, /obj/item/organ/brain)) //brain takes some extra damage
+ applyOrganDamage(25)
+ if(!iszombie(M)) //brains...
+ M.adjust_disgust(50)
+ else if(istype(src, /obj/item/organ/heart)) //heart makes a puddle of blood
+ M.add_splatter_floor(get_turf(src))
+
+ var/obj/item/reagent_containers/food/snacks/S = source_item
+ if(S?.tastes?.len && istype(S))
+ S.tastes += "meat"
+ S.tastes["meat"] = 3
+
+ if(organ_flags & ORGAN_EDIBLE)
+ var/datum/component/edible/EC = src.GetComponent(/datum/component/edible)
+ EC.checkLiked(1, M)
+
+ //people who like gross food or are voracious (voracious people wouldn't even notice)
+ if(((M.dna.species.liked_food & GROSS) && (M.dna.species.liked_food & MEAT)) || M.has_quirk(/datum/quirk/voracious))
+ M.visible_message("[M] looks like [M.p_theyve()] just bitten into something strange.", \
+ "Huh, did I just bite into a [name]?")
+ else
+ M.visible_message("[M] looks like [M.p_theyve()] just bitten into something awful!", \
+ "Ew!! Did I just bite into \a [name]?!")
+
+ if((damage >= maxHealth) && !istype(src, /obj/item/organ/brain)) //don't qdel brains
+ discover_after = FALSE
+ qdel(src) //oops, all gone
+
+ return discover_after
+
/obj/item/organ/item_action_slot_check(slot,mob/user)
return //so we don't grant the organ's action to mobs who pick up the organ.
@@ -225,3 +267,7 @@
*/
/obj/item/organ/proc/get_availability(datum/species/S)
return TRUE
+
+/// Called before organs are replaced in regenerate_organs with new ones
+/obj/item/organ/proc/before_organ_replacement(obj/item/organ/replacement)
+ return
diff --git a/code/modules/surgery/repair_puncture.dm b/code/modules/surgery/repair_puncture.dm
new file mode 100644
index 00000000000..12aefefc822
--- /dev/null
+++ b/code/modules/surgery/repair_puncture.dm
@@ -0,0 +1,108 @@
+
+/////BURN FIXING SURGERIES//////
+
+//the step numbers of each of these two, we only currently use the first to switch back and forth due to advancing after finishing steps anyway
+#define REALIGN_INNARDS 1
+#define WELD_VEINS 2
+
+///// Repair puncture wounds
+/datum/surgery/repair_puncture
+ name = "Repair puncture"
+ steps = list(/datum/surgery_step/incise, /datum/surgery_step/repair_innards, /datum/surgery_step/seal_veins, /datum/surgery_step/close) // repeat between steps 2 and 3 until healed
+ target_mobtypes = list(/mob/living/carbon)
+ possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
+ requires_real_bodypart = TRUE
+ targetable_wound = /datum/wound/pierce
+
+/datum/surgery/repair_puncture/can_start(mob/living/user, mob/living/carbon/target)
+ . = ..()
+ if(.)
+ var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected)
+ var/datum/wound/burn/pierce_wound = targeted_bodypart.get_wound_type(targetable_wound)
+ return(pierce_wound && pierce_wound.blood_flow > 0)
+
+//SURGERY STEPS
+
+///// realign the blood vessels so we can reweld them
+/datum/surgery_step/repair_innards
+ name = "realign blood vessels"
+ implements = list(TOOL_HEMOSTAT = 100, TOOL_SCALPEL = 85, TOOL_WIRECUTTER = 40)
+ time = 3 SECONDS
+
+/datum/surgery_step/repair_innards/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ if(!pierce_wound)
+ user.visible_message("[user] looks for [target]'s [parse_zone(user.zone_selected)].", "You look for [target]'s [parse_zone(user.zone_selected)]...")
+ return
+
+ if(pierce_wound.blood_flow <= 0)
+ to_chat(user, "[target]'s [parse_zone(user.zone_selected)] has no puncture to repair!")
+ surgery.status++
+ return
+
+ display_results(user, target, "You begin to realign the torn blood vessels in [target]'s [parse_zone(user.zone_selected)]...",
+ "[user] begins to realign the torn blood vessels in [target]'s [parse_zone(user.zone_selected)] with [tool].",
+ "[user] begins to realign the torn blood vessels in [target]'s [parse_zone(user.zone_selected)].")
+
+/datum/surgery_step/repair_innards/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE)
+ var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ if(!pierce_wound)
+ to_chat(user, "[target] has no puncture wound there!")
+ return ..()
+
+ display_results(user, target, "You successfully realign some of the blood vessels in [target]'s [parse_zone(target_zone)].",
+ "[user] successfully realigns some of the blood vessels in [target]'s [parse_zone(target_zone)] with [tool]!",
+ "[user] successfully realigns some of the blood vessels in [target]'s [parse_zone(target_zone)]!")
+ log_combat(user, target, "excised infected flesh in", addition="INTENT: [uppertext(user.a_intent)]")
+ surgery.operated_bodypart.receive_damage(brute=3, wound_bonus=CANT_WOUND)
+ pierce_wound.blood_flow -= 0.25
+ return ..()
+
+/datum/surgery_step/repair_innards/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, var/fail_prob = 0)
+ . = ..()
+ display_results(user, target, "You jerk apart some of the blood vessels in [target]'s [parse_zone(target_zone)].",
+ "[user] jerks apart some of the blood vessels in [target]'s [parse_zone(target_zone)] with [tool]!",
+ "[user] jerk apart some of the blood vessels in [target]'s [parse_zone(target_zone)]!")
+ surgery.operated_bodypart.receive_damage(brute=rand(4,8), sharpness=SHARP_EDGED, wound_bonus = 10)
+
+///// Sealing the vessels back together
+/datum/surgery_step/seal_veins
+ name = "weld veins" // if your doctor says they're going to weld your blood vessels back together, you're either A) on SS13, or B) in grave mortal peril
+ implements = list(TOOL_CAUTERY = 100, /obj/item/gun/energy/laser = 90, TOOL_WELDER = 70, /obj/item = 30)
+ time = 4 SECONDS
+
+/datum/surgery_step/seal_veins/tool_check(mob/user, obj/item/tool)
+ if(implement_type == TOOL_WELDER || implement_type == /obj/item)
+ return tool.get_temperature()
+
+ return TRUE
+
+/datum/surgery_step/seal_veins/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ if(!pierce_wound)
+ user.visible_message("[user] looks for [target]'s [parse_zone(user.zone_selected)].", "You look for [target]'s [parse_zone(user.zone_selected)]...")
+ return
+ display_results(user, target, "You begin to meld some of the split blood vessels in [target]'s [parse_zone(user.zone_selected)]...",
+ "[user] begins to meld some of the split blood vessels in [target]'s [parse_zone(user.zone_selected)] with [tool].",
+ "[user] begins to meld some of the split blood vessels in [target]'s [parse_zone(user.zone_selected)].")
+
+/datum/surgery_step/seal_veins/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE)
+ var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ if(!pierce_wound)
+ to_chat(user, "[target] has no puncture there!")
+ return ..()
+
+ display_results(user, target, "You successfully meld some of the split blood vessels in [target]'s [parse_zone(target_zone)] with [tool].",
+ "[user] successfully melds some of the split blood vessels in [target]'s [parse_zone(target_zone)] with [tool]!",
+ "[user] successfully melds some of the split blood vessels in [target]'s [parse_zone(target_zone)]!")
+ log_combat(user, target, "dressed burns in", addition="INTENT: [uppertext(user.a_intent)]")
+ pierce_wound.blood_flow -= 0.5
+ if(pierce_wound.blood_flow > 0)
+ surgery.status = REALIGN_INNARDS
+ to_chat(user, "There still seems to be misaligned blood vessels to finish...")
+ else
+ to_chat(user, "You've repaired all the internal damage in [target]'s [parse_zone(target_zone)]!")
+ return ..()
+
+#undef REALIGN_INNARDS
+#undef WELD_VEINS
diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm
index aad9b0a1da1..defacbe1cd4 100644
--- a/code/modules/surgery/tools.dm
+++ b/code/modules/surgery/tools.dm
@@ -110,7 +110,7 @@
custom_materials = list(/datum/material/iron=4000, /datum/material/glass=1000)
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP_ACCURATE
+ sharpness = SHARP_EDGED
tool_behaviour = TOOL_SCALPEL
toolspeed = 1
bare_wound_bonus = 20
@@ -146,7 +146,7 @@
throw_range = 5
custom_materials = list(/datum/material/iron=1000)
attack_verb = list("attacked", "slashed", "sawed", "cut")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
tool_behaviour = TOOL_SAW
toolspeed = 1
wound_bonus = 10
@@ -259,7 +259,7 @@
force = 16
toolspeed = 0.7
light_color = LIGHT_COLOR_GREEN
- sharpness = IS_SHARP_ACCURATE
+ sharpness = SHARP_EDGED
/obj/item/scalpel/advanced/Initialize()
. = ..()
@@ -349,7 +349,7 @@
throw_range = 5
custom_materials = list(/datum/material/iron=8000, /datum/material/titanium=6000)
attack_verb = list("sheared", "snipped")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
custom_premium_price = 1800
/obj/item/shears/attack(mob/living/M, mob/user)
diff --git a/code/modules/swarmers/swarmer.dm b/code/modules/swarmers/swarmer.dm
new file mode 100644
index 00000000000..67dc4aa9ac2
--- /dev/null
+++ b/code/modules/swarmers/swarmer.dm
@@ -0,0 +1,457 @@
+/**
+ * # Swarmer
+ *
+ * Tiny machines made by an ancient civilization, they seek only to consume materials and replicate.
+ *
+ * Tiny robots which, while not lethal, seek to destroy station components in order to recycle them into more swarmers.
+ * Sentient player swarmers spawn from a beacon spawned in maintenance and they can spawn melee swarmers to protect them.
+ * Swarmers have the following abilities:
+ * - Can melee targets to deal stamina damage. Stuns cyborgs.
+ * - Can teleport friend and foe alike away using ctrl + click. Applies binds to carbons, preventing them from immediate retaliation
+ * - Can shoot lasers which deal stamina damage to carbons and direct damage to simple mobs
+ * - Can self repair for free, completely healing themselves
+ * - Can construct traps which stun targets, and walls which block non-swarmer entites and projectiles
+ * - Can create swarmer drones, which lack the above abilities sans melee stunning targets. A swarmer can order its drones around by middle-clicking a tile.
+ */
+
+/mob/living/simple_animal/hostile/swarmer
+ name = "swarmer"
+ icon = 'icons/mob/swarmer.dmi'
+ desc = "Robotic constructs of unknown design, swarmers seek only to consume materials and replicate themselves indefinitely."
+ speak_emote = list("tones")
+ initial_language_holder = /datum/language_holder/swarmer
+ bubble_icon = "swarmer"
+ mob_biotypes = MOB_ROBOTIC
+ health = 40
+ maxHealth = 40
+ status_flags = CANPUSH
+ icon_state = "swarmer"
+ icon_living = "swarmer"
+ icon_dead = "swarmer_unactivated"
+ icon_gib = null
+ wander = 0
+ harm_intent_damage = 5
+ minbodytemp = 0
+ maxbodytemp = 500
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ unsuitable_atmos_damage = 0
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ melee_damage_type = STAMINA
+ damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
+ hud_possible = list(ANTAG_HUD, DIAG_STAT_HUD, DIAG_HUD)
+ obj_damage = 0
+ environment_smash = ENVIRONMENT_SMASH_NONE
+ attack_verb_continuous = "shocks"
+ attack_verb_simple = "shock"
+ attack_sound = 'sound/effects/empulse.ogg'
+ friendly_verb_continuous = "pinches"
+ friendly_verb_simple = "pinch"
+ speed = 0
+ faction = list("swarmer")
+ AIStatus = AI_OFF
+ pass_flags = PASSTABLE
+ mob_size = MOB_SIZE_TINY
+ ventcrawler = VENTCRAWLER_ALWAYS
+ ranged = 1
+ projectiletype = /obj/projectile/beam/disabler/swarmer
+ ranged_cooldown_time = 20
+ projectilesound = 'sound/weapons/taser2.ogg'
+ loot = list(/obj/effect/decal/cleanable/robot_debris, /obj/item/stack/ore/bluespace_crystal)
+ del_on_death = 1
+ deathmessage = "explodes with a sharp pop!"
+ light_color = LIGHT_COLOR_CYAN
+ hud_type = /datum/hud/swarmer
+ speech_span = SPAN_ROBOT
+ ///Resource points, generated by consuming metal/glass
+ var/resources = 0
+ ///Maximum amount of resources a swarmer can store
+ var/max_resources = 100
+ ///List used for player swarmers to keep track of their drones
+ var/list/mob/living/simple_animal/hostile/swarmer/melee/dronelist
+
+/mob/living/simple_animal/hostile/swarmer/Initialize()
+ . = ..()
+ verbs -= /mob/living/verb/pulled
+ for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
+ diag_hud.add_to_hud(src)
+
+/mob/living/simple_animal/hostile/swarmer/med_hud_set_health()
+ var/image/holder = hud_list[DIAG_HUD]
+ var/icon/I = icon(icon, icon_state, dir)
+ holder.pixel_y = I.Height() - world.icon_size
+ holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]"
+
+/mob/living/simple_animal/hostile/swarmer/med_hud_set_status()
+ var/image/holder = hud_list[DIAG_STAT_HUD]
+ var/icon/I = icon(icon, icon_state, dir)
+ holder.pixel_y = I.Height() - world.icon_size
+ holder.icon_state = "hudstat"
+
+/mob/living/simple_animal/hostile/swarmer/Stat()
+ ..()
+ if(statpanel("Status"))
+ stat("Resources:",resources)
+
+/mob/living/simple_animal/hostile/swarmer/emp_act()
+ . = ..()
+ if(. & EMP_PROTECT_SELF)
+ return
+ if(health > 1)
+ adjustHealth(health-1)
+ else
+ death()
+
+/mob/living/simple_animal/hostile/swarmer/CanAllowThrough(atom/movable/O)
+ . = ..()
+ if(istype(O, /obj/projectile/beam/disabler))//Allows for swarmers to fight as a group without wasting their shots hitting each other
+ return TRUE
+ if(isswarmer(O))
+ return TRUE
+
+////CTRL CLICK FOR SWARMERS AND SWARMER_ACT()'S////
+/mob/living/simple_animal/hostile/swarmer/AttackingTarget()
+ if(!isliving(target))
+ return target.swarmer_act(src)
+ if(iscyborg(target))
+ var/mob/living/silicon/borg = target
+ borg.adjustBruteLoss(melee_damage_lower)
+ return ..()
+
+/mob/living/simple_animal/hostile/swarmer/MiddleClickOn(atom/A)
+ . = ..()
+ if(!LAZYLEN(dronelist))
+ return
+ var/turf/clicked_turf = get_turf(A)
+ if(!clicked_turf)
+ return
+ for(var/d in dronelist)
+ var/mob/living/simple_animal/hostile/drone = d
+ drone.LoseTarget()
+ drone.Goto(clicked_turf, drone.move_to_delay)
+
+/mob/living/simple_animal/hostile/swarmer/CtrlClickOn(atom/A)
+ face_atom(A)
+ if(!isturf(loc))
+ return
+ if(next_move > world.time)
+ return
+ if(!A.Adjacent(src))
+ return
+ prepare_target(src)
+
+////END CTRL CLICK FOR SWARMERS////
+
+/**
+ * Called when a swarmer creates a structure or drone
+ *
+ * Proc called whenever a swarmer creates a structure or drone
+ * Arguments:
+ * * fabrication_object - The atom to create
+ * * fabrication_cost - How many resources it costs for a swarmer to create the object
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/Fabricate(atom/fabrication_object,fabrication_cost = 0)
+ if(!isturf(loc))
+ to_chat(src, "This is not a suitable location for fabrication. We need more space.")
+ return
+ if(resources < fabrication_cost)
+ to_chat(src, "You do not have the necessary resources to fabricate this object.")
+ return
+ resources -= fabrication_cost
+ return new fabrication_object(drop_location())
+
+/**
+ * Called when a swarmer attempts to consume an object
+ *
+ * Proc which determines interaction between a swarmer and whatever it is attempting to consume
+ * Arguments:
+ * * target - The material or object the swarmer is attempting to consume
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/Integrate(obj/target)
+ var/resource_gain = target.integrate_amount()
+ if(resources + resource_gain > max_resources)
+ to_chat(src, "We cannot hold more materials!")
+ return TRUE
+ if(!resource_gain)
+ to_chat(src, "[target] is incompatible with our internal matter recycler.")
+ return FALSE
+ resources += resource_gain
+ do_attack_animation(target)
+ changeNext_move(CLICK_CD_RAPID)
+ var/obj/effect/temp_visual/swarmer/integrate/I = new /obj/effect/temp_visual/swarmer/integrate(get_turf(target))
+ I.pixel_x = target.pixel_x
+ I.pixel_y = target.pixel_y
+ I.pixel_z = target.pixel_z
+ if(istype(target, /obj/item/stack))
+ var/obj/item/stack/S = target
+ S.use(1)
+ if(S.amount)
+ return TRUE
+ qdel(target)
+ return TRUE
+
+/**
+ * Called when a swarmer attempts to destroy a structure
+ *
+ * Proc which determines interaction between a swarmer and a structure it is destroying
+ * Arguments:
+ * * target - The material or object the swarmer is attempting to destroy
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/dis_integrate(atom/movable/target)
+ new /obj/effect/temp_visual/swarmer/disintegration(get_turf(target))
+ do_attack_animation(target)
+ changeNext_move(CLICK_CD_MELEE)
+ SSexplosions.lowobj += target
+
+/**
+ * Called when a swarmer attempts to teleport a living entity away
+ *
+ * Proc which finds a safe location to teleport a living entity to when a swarmer teleports it away. Also energy handcuffs carbons.
+ * Arguments:
+ * * target - The entity the swarmer is trying to teleport away
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/prepare_target(mob/living/target)
+ if(target == src)
+ return
+
+ if(!is_station_level(z) && !is_mining_level(z))
+ to_chat(src, "Our bluespace transceiver cannot locate a viable bluespace link, our teleportation abilities are useless in this area.")
+ return
+
+ to_chat(src, "Attempting to remove this being from our presence.")
+
+ if(!do_mob(src, target, 30))
+ return
+
+ teleport_target(target)
+
+/mob/living/simple_animal/hostile/swarmer/proc/teleport_target(mob/living/target)
+ var/turf/open/floor/safe_turf = find_safe_turf(zlevels = z, extended_safety_checks = TRUE)
+
+ if(!safe_turf )
+ return
+ // If we're getting rid of a human, slap some energy cuffs on
+ // them to keep them away from us a little longer
+
+ if(ishuman(target))
+ var/mob/living/carbon/human/victim = target
+ if(!victim.handcuffed)
+ victim.handcuffed = new /obj/item/restraints/handcuffs/energy/used(victim)
+ victim.update_handcuffed()
+ log_combat(src, victim, "handcuffed")
+
+ var/datum/effect_system/spark_spread/sparks = new
+ sparks.set_up(4,0,get_turf(target))
+ sparks.start()
+ playsound(src, 'sound/effects/sparks4.ogg', 50, TRUE)
+ do_teleport(target, safe_turf , 0, channel = TELEPORT_CHANNEL_BLUESPACE)
+
+/mob/living/simple_animal/hostile/swarmer/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE)
+ if(!(flags & SHOCK_TESLA))
+ return FALSE
+ return ..()
+
+/**
+ * Called when a swarmer attempts to disassemble a machine
+ *
+ * Proc called when a swarmer attempts to disassemble a machine. Destroys the machine, and gives the swarmer metal.
+ * Arguments:
+ * * target - The machine the swarmer is attempting to disassemble
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/dismantle_machine(obj/machinery/target)
+ do_attack_animation(target)
+ to_chat(src, "We begin to dismantle this machine. We will need to be uninterrupted.")
+ var/obj/effect/temp_visual/swarmer/dismantle/dismantle_effect = new /obj/effect/temp_visual/swarmer/dismantle(get_turf(target))
+ dismantle_effect.pixel_x = target.pixel_x
+ dismantle_effect.pixel_y = target.pixel_y
+ dismantle_effect.pixel_z = target.pixel_z
+ if(do_mob(src, target, 100))
+ to_chat(src, "Dismantling complete.")
+ var/atom/target_loc = target.drop_location()
+ new /obj/item/stack/sheet/metal(target_loc, 5)
+ for(var/p in target.component_parts)
+ var/obj/item/part = p
+ part.forceMove(target_loc)
+ var/obj/effect/temp_visual/swarmer/disintegration/disintegration_effect = new /obj/effect/temp_visual/swarmer/disintegration(get_turf(target))
+ disintegration_effect.pixel_x = target.pixel_x
+ disintegration_effect.pixel_y = target.pixel_y
+ disintegration_effect.pixel_z = target.pixel_z
+ target.dropContents()
+ if(istype(target, /obj/machinery/computer))
+ var/obj/machinery/computer/computer_target = target
+ if(computer_target.circuit)
+ computer_target.circuit.forceMove(target_loc)
+ qdel(target)
+
+/**
+ * Called when a swarmer attempts to create a trap
+ *
+ * Proc used to allow a swarmer to create a trap. Checks if a trap is on the tile, then if the swarmer can afford, and then places the trap.
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/create_trap()
+ set name = "Create trap"
+ set category = "Swarmer"
+ set desc = "Creates a simple trap that will non-lethally electrocute anything that steps on it. Costs 4 resources."
+ if(locate(/obj/structure/swarmer/trap) in loc)
+ to_chat(src, "There is already a trap here. Aborting.")
+ return
+ if(resources < 4)
+ to_chat(src, "We do not have the resources for this!")
+ return
+ Fabricate(/obj/structure/swarmer/trap, 4)
+
+/**
+ * Called when a swarmer attempts to create a barricade
+ *
+ * Proc used to allow a swarmer to create a barricade. Checks if a barricade is on the tile, then if the swarmer can afford it, and then will attempt to create a barricade after a second delay.
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/create_barricade()
+ set name = "Create barricade"
+ set category = "Swarmer"
+ set desc = "Creates a barricade that will stop anything but swarmers and disabler beams from passing through. Costs 4 resources."
+ if(locate(/obj/structure/swarmer/blockade) in loc)
+ to_chat(src, "There is already a blockade here. Aborting.")
+ return
+ if(resources < 4)
+ to_chat(src, "We do not have the resources for this!")
+ return
+ if(!do_mob(src, src, 1 SECONDS))
+ return
+ Fabricate(/obj/structure/swarmer/blockade, 4)
+
+/**
+ * Called when a swarmer attempts to create a drone
+ *
+ * Proc used to allow a swarmer to create a drone. Checks if the swarmer can afford the drone, then creates it after 5 seconds, and also registers it to the creating swarmer so it can command it
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/create_swarmer()
+ set name = "Replicate"
+ set category = "Swarmer"
+ set desc = "Creates a duplicate of ourselves, capable of protecting us while we complete our objectives."
+ to_chat(src, "We are attempting to replicate ourselves. We will need to stand still until the process is complete.")
+ if(resources < 20)
+ to_chat(src, "We do not have the resources for this!")
+ return
+ if(!isturf(loc))
+ to_chat(src, "This is not a suitable location for replicating ourselves. We need more room.")
+ return
+ if(!do_mob(src, src, 5 SECONDS))
+ return
+ var/createtype = swarmer_type_to_create()
+ if(!createtype)
+ return
+ var/mob/newswarmer = Fabricate(createtype, 20)
+ add_drone(newswarmer)
+ playsound(loc,'sound/items/poster_being_created.ogg', 20, TRUE, -1)
+
+/**
+ * Used to determine what type of swarmer a swarmer should create
+ *
+ * Returns the type of the swarmer to be created
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/swarmer_type_to_create()
+ return /mob/living/simple_animal/hostile/swarmer/melee
+
+/**
+ * Called when a swarmer attempts to repair itself
+ *
+ * Proc used to allow a swarmer self-repair. If the swarmer does not move after a period of time, then it will heal fully
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/repair_self()
+ if(!isturf(loc))
+ return
+ to_chat(src, "Attempting to repair damage to our body, stand by...")
+ if(!do_mob(src, src, 10 SECONDS))
+ return
+ adjustHealth(-maxHealth)
+ to_chat(src, "We successfully repaired ourselves.")
+
+/**
+ * Called when a swarmer toggles its light
+ *
+ * Proc used to allow a swarmer to toggle its light on and off. If a swarmer has any drones, change their light settings to match their master's.
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/toggle_light()
+ if(!light_range)
+ set_light(3)
+ if(!mind)
+ return
+ for(var/d in dronelist)
+ var/mob/living/simple_animal/hostile/swarmer/melee/drone = d
+ drone.set_light(3)
+ else
+ set_light(0)
+ if(!mind)
+ return
+ for(var/d in dronelist)
+ var/mob/living/simple_animal/hostile/swarmer/melee/drone = d
+ drone.set_light(0)
+
+/**
+ * Proc which is used for swarmer comms
+ *
+ * Proc called which sends a message to all other swarmers.
+ * Arugments:
+ * * msg - The message the swarmer is sending, gotten from ContactSwarmers()
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/swarmer_chat(msg)
+ var/rendered = "Swarm communication - [src] [say_quote(msg)]"
+ for(var/i in GLOB.mob_list)
+ var/mob/listener = i
+ if(isswarmer(listener))
+ to_chat(listener, rendered)
+ else if(isobserver(listener))
+ var/link = FOLLOW_LINK(listener, src)
+ to_chat(listener, "[link] [rendered]")
+
+/**
+ * Proc which is used for inputting a swarmer message
+ *
+ * Proc which is used for a swarmer to input a message on a pop-up box, then attempt to send that message to the other swarmers
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/contact_swarmers()
+ var/message = stripped_input(src, "Announce to other swarmers", "Swarmer contact")
+ // TODO get swarmers their own colour rather than just boldtext
+ if(message)
+ swarmer_chat(message)
+
+
+///Adds a drone to the swarmer list and keeps track of it in case it's deleted and requires cleanup.
+/mob/living/simple_animal/hostile/swarmer/proc/add_drone(mob/newswarmer)
+ LAZYADD(dronelist, newswarmer)
+ RegisterSignal(newswarmer, COMSIG_PARENT_QDELETING, .proc/remove_drone, newswarmer)
+
+
+/**
+ * Removes a drone from the swarmer's list.
+ *
+ * Removes the drone from our list.
+ * Called specifically when a drone is about to be destroyed, so we don't have any null references.
+ * Arguments:
+ * * mob/drone - The drone to be removed from the list.
+ */
+/mob/living/simple_animal/hostile/swarmer/proc/remove_drone(mob/drone, force)
+ UnregisterSignal(drone, COMSIG_PARENT_QDELETING)
+ dronelist -= drone
+
+/**
+ * # Swarmer Drone
+ *
+ * Melee subtype of swarmers, always AI-controlled under normal circumstances. Cannot fire projectiles, but does double stamina damage on melee
+ */
+/mob/living/simple_animal/hostile/swarmer/melee
+ icon_state = "swarmer_melee"
+ icon_living = "swarmer_melee"
+ ranged = FALSE
+ AIStatus = AI_ON
+ melee_damage_lower = 30
+ melee_damage_upper = 30
+
+/obj/projectile/beam/disabler/swarmer/on_hit(atom/target, blocked = FALSE)
+ . = ..()
+ if(!.)
+ return
+ if(!istype(target, /mob/living/simple_animal) || !istype(firer, /mob/living/simple_animal/hostile/swarmer))
+ return
+ var/mob/living/simple_animal/hostile/swarmer/swarmer = firer
+ swarmer.teleport_target(target)
diff --git a/code/modules/swarmers/swarmer_act.dm b/code/modules/swarmers/swarmer_act.dm
new file mode 100644
index 00000000000..6590cacd9d5
--- /dev/null
+++ b/code/modules/swarmers/swarmer_act.dm
@@ -0,0 +1,231 @@
+/**
+ * Determines what happens to an atom when a swarmer interacts with it
+ *
+ * Determines behavior upon being interacted on by a swarmer.
+ * Arguments:
+ * * S - A reference to the swarmer doing the interaction
+ */
+/atom/proc/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ actor.dis_integrate(src)
+ return TRUE //return TRUE/FALSE whether or not an AI swarmer should try this swarmer_act() again, NOT whether it succeeded.
+
+/obj/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ if(resistance_flags & INDESTRUCTIBLE)
+ return FALSE
+ for(var/mob/living/living_content in contents)
+ if(issilicon(living_content) || isbrain(living_content))
+ continue
+ to_chat(actor, "An organism has been detected inside this object. Aborting.")
+ return FALSE
+ return ..()
+
+/obj/item/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ return actor.Integrate(src)
+
+/**
+ * Return used to determine how many resources a swarmer gains when consuming an object
+ */
+/obj/proc/integrate_amount()
+ return 0
+
+/obj/item/integrate_amount() //returns the amount of resources gained when eating this item
+ if(custom_materials && (custom_materials[SSmaterials.GetMaterialRef(/datum/material/iron)] || custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)]))
+ return 1
+ return ..()
+
+/obj/item/gun/swarmer_act()//Stops you from eating the entire armory
+ return FALSE
+
+/turf/open/swarmer_act()//ex_act() on turf calls it on its contents, this is to prevent attacking mobs by DisIntegrate()'ing the floor
+ return FALSE
+
+/obj/structure/lattice/catwalk/swarmer_catwalk/swarmer_act()
+ return FALSE
+
+/obj/structure/swarmer/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ if(actor.AIStatus == AI_ON)
+ return FALSE
+ return ..()
+
+/obj/effect/swarmer_act()
+ return FALSE
+
+/obj/effect/decal/cleanable/robot_debris/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ actor.dis_integrate(src)
+ qdel(src)
+ return TRUE
+
+/obj/structure/swarmer_beacon/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "This machine is required for further reproduction of swarmers. Aborting.")
+ return FALSE
+
+/obj/structure/flora/swarmer_act()
+ return FALSE
+
+/turf/open/lava/swarmer_act()
+ if(!is_safe())
+ new /obj/structure/lattice/catwalk/swarmer_catwalk(src)
+ return FALSE
+
+/obj/machinery/atmospherics/swarmer_act()
+ return FALSE
+
+/obj/structure/disposalpipe/swarmer_act()
+ return FALSE
+
+/obj/machinery/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ actor.dismantle_machine(src)
+ return TRUE
+
+/obj/machinery/light/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ actor.dis_integrate(src)
+ return TRUE
+
+/obj/machinery/door/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ var/isonshuttle = istype(get_area(src), /area/shuttle)
+ for(var/turf/turf_in_range in range(1, src))
+ var/area/turf_area = get_area(turf_in_range)
+ if(isspaceturf(turf_in_range) || (!isonshuttle && (istype(turf_area, /area/shuttle) || istype(turf_area, /area/space))) || (isonshuttle && !istype(turf_area, /area/shuttle)))
+ to_chat(actor, "Destroying this object has the potential to cause a hull breach. Aborting.")
+ actor.target = null
+ return FALSE
+ else if(istype(turf_area, /area/engine/supermatter))
+ to_chat(actor, "Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.")
+ actor.target = null
+ return FALSE
+ actor.dis_integrate(src)
+ return TRUE
+
+/obj/machinery/camera/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ actor.dis_integrate(src)
+ if(!QDELETED(actor)) //If it got blown up no need to turn it off.
+ toggle_cam(actor, FALSE)
+ return TRUE
+
+/obj/machinery/particle_accelerator/control_box/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ actor.dis_integrate(src)
+ return TRUE
+
+/obj/machinery/field/generator/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ actor.dis_integrate(src)
+ return TRUE
+
+/obj/machinery/gravity_generator/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ actor.dis_integrate(src)
+ return TRUE
+
+/obj/machinery/vending/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)//It's more visually interesting than dismantling the machine
+ actor.dis_integrate(src)
+ return TRUE
+
+/obj/machinery/turretid/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ actor.dis_integrate(src)
+ return TRUE
+
+/obj/machinery/chem_dispenser/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "The volatile chemicals in this machine would destroy us. Aborting.")
+ return FALSE
+
+/obj/machinery/nuclearbomb/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "This device's destruction would result in the extermination of everything in the area. Aborting.")
+ return FALSE
+
+/obj/effect/rune/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "Searching... sensor malfunction! Target lost. Aborting.")
+ return FALSE
+
+/obj/structure/reagent_dispensers/fueltank/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "Destroying this object could cause a chain reaction. Aborting.")
+ return FALSE
+
+/obj/structure/cable/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "Disrupting the power grid would bring no benefit to us. Aborting.")
+ return FALSE
+
+/obj/machinery/portable_atmospherics/canister/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "An inhospitable area may be created as a result of destroying this object. Aborting.")
+ return FALSE
+
+/obj/machinery/telecomms/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "This communications relay should be preserved, it will be a useful resource to our masters in the future. Aborting.")
+ return FALSE
+
+/obj/machinery/deepfryer/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "This kitchen appliance should be preserved, it will make delicious unhealthy snacks for our masters in the future. Aborting.")
+ return FALSE
+
+/obj/machinery/power/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "Disrupting the power grid would bring no benefit to us. Aborting.")
+ return FALSE
+
+/obj/machinery/gateway/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "This bluespace source will be important to us later. Aborting.")
+ return FALSE
+
+/turf/closed/wall/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ var/isonshuttle = istype(loc, /area/shuttle)
+ for(var/turf/turf_in_range in range(1, src))
+ var/area/turf_area = get_area(turf_in_range)
+ if(isspaceturf(turf_area) || (!isonshuttle && (istype(turf_area, /area/shuttle) || istype(turf_area, /area/space))) || (isonshuttle && !istype(turf_area, /area/shuttle)))
+ to_chat(actor, "Destroying this object has the potential to cause a hull breach. Aborting.")
+ actor.target = null
+ return TRUE
+ else if(istype(turf_area, /area/engine/supermatter))
+ to_chat(actor, "Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.")
+ actor.target = null
+ return TRUE
+ return ..()
+
+/obj/structure/window/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ var/is_on_shuttle = istype(get_area(src), /area/shuttle)
+ for(var/turf/adj_turf in range(1, src))
+ var/area/adj_area = get_area(adj_turf)
+ if(isspaceturf(adj_turf) || (!is_on_shuttle && (istype(adj_area, /area/shuttle) || istype(adj_area, /area/space))) || (is_on_shuttle && !istype(adj_area, /area/shuttle)))
+ to_chat(actor, "Destroying this object has the potential to cause a hull breach. Aborting.")
+ actor.target = null
+ return TRUE
+ if(istype(adj_area, /area/engine/supermatter))
+ to_chat(actor, "Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.")
+ actor.target = null
+ return TRUE
+ return ..()
+
+/obj/item/stack/cable_coil/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)//Wiring would be too effective as a resource
+ to_chat(actor, "This object does not contain enough materials to work with.")
+ return FALSE
+
+/obj/machinery/porta_turret/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "Attempting to dismantle this machine would result in an immediate counterattack. Aborting.")
+ return FALSE
+
+/obj/machinery/porta_turret_cover/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "Attempting to dismantle this machine would result in an immediate counterattack. Aborting.")
+ return FALSE
+
+/obj/structure/lattice/catwalk/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ var/turf/here = get_turf(src)
+ for(var/a in here.contents)
+ if(istype(a, /obj/structure/cable))
+ to_chat(actor, "Disrupting the power grid would bring no benefit to us. Aborting.")
+ return FALSE
+ return ..()
+
+/obj/machinery/hydroponics/soil/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "This object does not contain enough materials to work with.")
+ return FALSE
+
+/obj/machinery/field/generator/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "Destroying this object would cause a catastrophic chain reaction. Aborting.")
+ return FALSE
+
+/obj/machinery/field/containment/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "This object does not contain solid matter. Aborting.")
+ return FALSE
+
+/obj/machinery/power/shieldwallgen/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "Destroying this object would have an unpredictable effect on structure integrity. Aborting.")
+ return FALSE
+
+/obj/machinery/shieldwall/swarmer_act(mob/living/simple_animal/hostile/swarmer/actor)
+ to_chat(actor, "This object does not contain solid matter. Aborting.")
+ return FALSE
diff --git a/code/modules/swarmers/swarmer_objs.dm b/code/modules/swarmers/swarmer_objs.dm
new file mode 100644
index 00000000000..ea67eff04b0
--- /dev/null
+++ b/code/modules/swarmers/swarmer_objs.dm
@@ -0,0 +1,153 @@
+/obj/structure/swarmer //Default swarmer effect object visual feedback
+ name = "swarmer ui"
+ desc = null
+ gender = NEUTER
+ icon = 'icons/mob/swarmer.dmi'
+ icon_state = "ui_light"
+ layer = MOB_LAYER
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ light_color = LIGHT_COLOR_CYAN
+ max_integrity = 30
+ anchored = TRUE
+ ///How strong the light effect for the structure is
+ var/glow_range = 1
+
+/obj/structure/swarmer/Initialize(mapload)
+ . = ..()
+ set_light(glow_range)
+
+/obj/structure/swarmer/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = NONE)
+ switch(damage_type)
+ if(BRUTE)
+ playsound(src, 'sound/weapons/egloves.ogg', 80, TRUE)
+ if(BURN)
+ playsound(src, 'sound/items/welder.ogg', 100, TRUE)
+
+/obj/structure/swarmer/emp_act()
+ . = ..()
+ if(. & EMP_PROTECT_SELF)
+ return
+ qdel(src)
+
+/**
+ * # Swarmer Beacon
+ *
+ * Beacon which creates sentient player swarmers.
+ *
+ * The beacon which creates sentient player swarmers during the swarmer event. Spawns in maint on xeno locations, and can create a player swarmer once every 30 seconds.
+ * The beacon cannot be damaged by swarmers, and must be destroyed to prevent the spawning of further player-controlled swarmers.
+ * Holds a swarmer within itself during the 30 seconds before releasing it and allowing for another swarmer to be spawned in.
+ */
+
+/obj/structure/swarmer_beacon
+ name = "swarmer beacon"
+ desc = "A machine that prints swarmers."
+ icon = 'icons/mob/swarmer.dmi'
+ icon_state = "swarmer_console"
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ max_integrity = 400
+ layer = MASSIVE_OBJ_LAYER
+ light_color = LIGHT_COLOR_CYAN
+ light_range = 10
+ anchored = TRUE
+ density = FALSE
+ ///Whether or not a swarmer is currently being created by this beacon
+ var/processing_swarmer = FALSE
+
+/obj/structure/swarmer_beacon/attack_ghost(mob/user)
+ . = ..()
+ if(processing_swarmer)
+ to_chat(user, "A swarmer is currently being created. Try again soon.")
+ return
+ que_swarmer(user)
+
+/**
+ * Interaction when a ghost interacts with a swarmer beacon
+ *
+ * Called when a ghost interacts with a swarmer beacon, allowing them to become a swarmer
+ * Arguments:
+ * * user - A reference to the ghost interacting with the beacon
+ */
+/obj/structure/swarmer_beacon/proc/que_swarmer(mob/user)
+ var/swarm_ask = alert("Become a swarmer?", "Do you wish to consume the station?", "Yes", "No")
+ if(swarm_ask == "No" || QDELETED(src) || QDELETED(user) || processing_swarmer)
+ return FALSE
+ var/mob/living/simple_animal/hostile/swarmer/newswarmer = new /mob/living/simple_animal/hostile/swarmer(src)
+ newswarmer.key = user.key
+ addtimer(CALLBACK(src, .proc/release_swarmer, newswarmer), 30 SECONDS)
+ to_chat(newswarmer, "SWARMER CONSTURCTION INITIALIZED. TIME TO COMPLETION: 30 SECONDS")
+ processing_swarmer = TRUE
+ return TRUE
+
+/**
+ * Releases a swarmer from the beacon and tells it what to do
+ *
+ * Occcurs 30 seconds after a ghost becomes a swarmer. The beacon releases it, tells it what to do, and opens itself up to spawn in a new swarmer.
+ * Arguments:
+ * * swarmer - The swarmer being released and told what to do
+ */
+/obj/structure/swarmer_beacon/proc/release_swarmer(mob/swarmer)
+ to_chat(swarmer, "SWARMER CONSTURCTION COMPLETED. OBJECTIVES:\n\
+ 1. CONSUME RESOURCES AND REPLICATE UNTIL THERE ARE NO MORE RESOURCES LEFT\n\
+ 2. ENSURE PROTECTION OF THE BEACON SO THIS LOCATION CAN BE INVADED AT A LATER DATE; DO NOT PERFORM ACTIONS THAT WOULD RENDER THIS LOCATION DANGEROUS OR INHOSPITABLE\n\
+ 3. BIOLOGICAL RESOURCES WILL BE HARVESTED AT A LATER DATE: DO NOT HARM THEM\n\
+ OPERATOR NOTES:\n\
+ - CONSUME RESOURCES TO CONSTRUCT TRAPS, BARRIERS, AND FOLLOWER DRONES\n\
+ - FOLLOWER DRONES CAN BE ORDERED TO MOVE VIA MIDDLE CLICKING ON A TILE. WHILE DRONES CANNOT ASSIST IN RESOURCE HARVESTING, THEY CAN PROTECT YOU FROM THREATS\n\
+ - LCTRL + ATTACKING AN ORGANIC WILL ALOW YOU TO REMOVE SAID ORGANIC FROM THE AREA\n\
+ - YOU AND YOUR DRONES HAVE A STUN EFFECT ON MELEE. YOU ARE ALSO ARMED WITH A DISABLER PROJECTILE, USE THESE TO PREVENT ORGANICS FROM HALTING YOUR PROGRESS\n\
+ GLORY TO !*# $*#^")
+ swarmer.forceMove(get_turf(src))
+ processing_swarmer = FALSE
+
+/obj/structure/swarmer/trap
+ name = "swarmer trap"
+ desc = "A quickly assembled trap that electrifies living beings and overwhelms machine sensors. Will not retain its form if damaged enough."
+ icon_state = "trap"
+ max_integrity = 10
+ density = FALSE
+
+/obj/structure/swarmer/trap/Crossed(atom/movable/AM)
+ if(isliving(AM))
+ var/mob/living/living_crosser = AM
+ if(!istype(living_crosser, /mob/living/simple_animal/hostile/swarmer))
+ playsound(loc,'sound/effects/snap.ogg',50, TRUE, -1)
+ living_crosser.electrocute_act(0, src, 1, flags = SHOCK_NOGLOVES|SHOCK_ILLUSION)
+ if(iscyborg(living_crosser))
+ living_crosser.Paralyze(100)
+ qdel(src)
+ return ..()
+
+/obj/structure/swarmer/blockade
+ name = "swarmer blockade"
+ desc = "A quickly assembled energy blockade. Will not retain its form if damaged enough, but disabler beams and swarmers pass right through."
+ icon_state = "barricade"
+ light_range = MINIMUM_USEFUL_LIGHT_RANGE
+ max_integrity = 50
+
+/obj/structure/swarmer/blockade/CanAllowThrough(atom/movable/O)
+ . = ..()
+ if(isswarmer(O))
+ return TRUE
+ if(istype(O, /obj/projectile/beam/disabler))
+ return TRUE
+
+/obj/effect/temp_visual/swarmer //temporary swarmer visual feedback objects
+ icon = 'icons/mob/swarmer.dmi'
+ layer = BELOW_MOB_LAYER
+
+/obj/effect/temp_visual/swarmer/disintegration
+ icon_state = "disintegrate"
+ duration = 1 SECONDS
+
+/obj/effect/temp_visual/swarmer/disintegration/Initialize()
+ . = ..()
+ playsound(loc, "sparks", 100, TRUE)
+
+/obj/effect/temp_visual/swarmer/dismantle
+ icon_state = "dismantle"
+ duration = 2.5 SECONDS
+
+/obj/effect/temp_visual/swarmer/integrate
+ icon_state = "integrate"
+ duration = 0.5 SECONDS
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
index 3390d26dec7..5eacfd07fe2 100644
--- a/code/modules/tgui/tgui.dm
+++ b/code/modules/tgui/tgui.dm
@@ -33,8 +33,6 @@
var/status = UI_INTERACTIVE
/// Topic state used to determine status/interactability.
var/datum/ui_state/state = null
- /// Asset data to be sent with every update
- var/list/asset_data
/**
* public
@@ -82,11 +80,14 @@
opened_at = world.time
window.acquire_lock(src)
if(!window.is_ready())
- window.initialize()
+ window.initialize(inline_assets = list(
+ get_asset_datum(/datum/asset/simple/tgui),
+ ))
else
window.send_message("ping")
+ window.send_asset(get_asset_datum(/datum/asset/simple/fontawesome))
for(var/datum/asset/asset in src_object.ui_assets(user))
- send_asset(asset)
+ window.send_asset(asset)
window.send_message("update", get_payload(
with_data = TRUE,
with_static_data = TRUE))
@@ -143,14 +144,10 @@
*
* required asset datum/asset
*/
-/datum/tgui/proc/send_asset(var/datum/asset/asset)
- if(!user.client)
- return
- if(istype(asset, /datum/asset/spritesheet))
- var/datum/asset/spritesheet/spritesheet = asset
- LAZYINITLIST(asset_data)
- LAZYADD(asset_data["styles"], list(spritesheet.css_filename()))
- asset.send(user)
+/datum/tgui/proc/send_asset(datum/asset/asset)
+ if(!window)
+ CRASH("send_asset() can only be called after open().")
+ window.send_asset(asset)
/**
* public
@@ -216,8 +213,6 @@
var/static_data = with_static_data && src_object.ui_static_data(user)
if(static_data)
json_data["static_data"] = static_data
- if(asset_data)
- json_data["assets"] = asset_data
if(src_object.tgui_shared_states)
json_data["shared"] = src_object.tgui_shared_states
return json_data
@@ -292,6 +287,8 @@
if(href_list["fatal"])
close(can_be_suspended = FALSE)
if("setSharedState")
+ if(status != UI_INTERACTIVE)
+ return
LAZYINITLIST(src_object.tgui_shared_states)
src_object.tgui_shared_states[href_list["key"]] = href_list["value"]
SStgui.update_uis(src_object)
diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm
index 1ef370317c7..3f271163c9c 100644
--- a/code/modules/tgui/tgui_window.dm
+++ b/code/modules/tgui/tgui_window.dm
@@ -13,6 +13,7 @@
var/datum/tgui/locked_by
var/fatally_errored = FALSE
var/message_queue
+ var/sent_assets = list()
/**
* public
@@ -36,8 +37,10 @@
* Initializes the window with a fresh page. Puts window into the "loading"
* state. You can begin sending messages right after initializing. Messages
* will be put into the queue until the window finishes loading.
+ *
+ * optional inline_assets list List of assets to inline into the html.
*/
-/datum/tgui_window/proc/initialize()
+/datum/tgui_window/proc/initialize(inline_assets = list())
log_tgui(client, "[id]/initialize")
if(!client)
return
@@ -52,13 +55,23 @@
else
options += "titlebar=1;can_resize=1;"
// Generate page html
- // TODO: Make this static
var/html = SStgui.basehtml
html = replacetextEx(html, "\[tgui:windowId]", id)
- // Send required assets
- var/datum/asset/asset
- asset = get_asset_datum(/datum/asset/group/tgui)
- asset.send(client)
+ // Process inline assets
+ var/inline_styles = ""
+ var/inline_scripts = ""
+ for(var/datum/asset/asset in inline_assets)
+ var/mappings = asset.get_url_mappings()
+ for(var/name in mappings)
+ var/url = mappings[name]
+ // Not urlencoding since asset strings are considered safe
+ if(copytext(name, -4) == ".css")
+ inline_styles += "\n"
+ else if(copytext(name, -3) == ".js")
+ inline_scripts += "\n"
+ asset.send()
+ html = replacetextEx(html, "\n", inline_styles)
+ html = replacetextEx(html, "\n", inline_scripts)
// Open the window
client << browse(html, "window=[id];[options]")
// Instruct the client to signal UI when the window is closed.
@@ -86,7 +99,7 @@
&& pooled \
&& pool_index > 0 \
&& pool_index <= TGUI_WINDOW_SOFT_LIMIT \
- && status >= TGUI_WINDOW_READY
+ && status == TGUI_WINDOW_READY
/**
* public
@@ -107,6 +120,9 @@
* Release the window lock.
*/
/datum/tgui_window/proc/release_lock()
+ // Clean up assets sent by tgui datum which requested the lock
+ if(locked)
+ sent_assets = list()
locked = FALSE
locked_by = null
@@ -126,8 +142,7 @@
send_message("suspend")
return
log_tgui(client, "[id]/close")
- locked = FALSE
- locked_by = null
+ release_lock()
status = TGUI_WINDOW_CLOSED
message_queue = null
// Do not close the window to give user some time
@@ -157,13 +172,30 @@
// Pack for sending via output()
message = url_encode(message)
// Place into queue if window is still loading
- if(!force && status == TGUI_WINDOW_LOADING)
+ if(!force && status != TGUI_WINDOW_READY)
if(!message_queue)
message_queue = list()
message_queue += list(message)
return
client << output(message, "[id].browser:update")
+/**
+ * public
+ *
+ * Makes an asset available to use in tgui.
+ *
+ * required asset datum/asset
+ */
+/datum/tgui_window/proc/send_asset(datum/asset/asset)
+ if(!client || !asset)
+ return
+ if(istype(asset, /datum/asset/spritesheet))
+ var/datum/asset/spritesheet/spritesheet = asset
+ send_message("asset/stylesheet", spritesheet.css_filename())
+ send_message("asset/mappings", asset.get_url_mappings())
+ sent_assets += list(asset)
+ asset.send(client)
+
/**
* private
*
@@ -184,6 +216,11 @@
/datum/tgui_window/proc/on_message(type, list/payload, list/href_list)
switch(type)
if("ready")
+ // Status can be READY if user has refreshed the window.
+ if(status == TGUI_WINDOW_READY)
+ // Resend the assets
+ for(var/asset in sent_assets)
+ send_asset(asset)
status = TGUI_WINDOW_READY
if("log")
if(href_list["fatal"])
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 5d7994f737c..45f634b11e4 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -15,10 +15,14 @@
#include "card_mismatch.dm"
#include "chain_pull_through_space.dm"
#include "component_tests.dm"
+#include "keybinding_init.dm"
+#include "medical_wounds.dm"
+#include "metabolizing.dm"
#include "outfit_sanity.dm"
#include "plantgrowth_tests.dm"
#include "reagent_id_typos.dm"
#include "reagent_recipe_collisions.dm"
+#include "say.dm"
#include "siunit.dm"
#include "spawn_humans.dm"
#include "species_whitelists.dm"
diff --git a/code/modules/unit_tests/keybinding_init.dm b/code/modules/unit_tests/keybinding_init.dm
new file mode 100644
index 00000000000..2bd2fdee1e2
--- /dev/null
+++ b/code/modules/unit_tests/keybinding_init.dm
@@ -0,0 +1,6 @@
+/datum/unit_test/keybinding_init/Run()
+ for(var/i in subtypesof(/datum/keybinding))
+ var/datum/keybinding/KB = i
+ if(initial(KB.keybind_signal) || !initial(KB.name))
+ continue
+ Fail("[KB.name] does not have a keybind signal defined.")
diff --git a/code/modules/unit_tests/medical_wounds.dm b/code/modules/unit_tests/medical_wounds.dm
new file mode 100644
index 00000000000..75c08931f16
--- /dev/null
+++ b/code/modules/unit_tests/medical_wounds.dm
@@ -0,0 +1,87 @@
+/// This test is used to make sure a flesh-and-bone base human can suffer all the types of wounds, and that suffering more severe wounds removes and replaces the lesser wound. Also tests that [/mob/living/carbon/proc/fully_heal] removes all wounds
+/datum/unit_test/test_human_base/Run()
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+
+ /// the limbs have no wound resistance like the chest and head do, so let's go with the r_arm
+ var/obj/item/bodypart/tested_part = victim.get_bodypart(BODY_ZONE_R_ARM)
+ /// In order of the wound types we're trying to inflict, what sharpness do we need to deal them?
+ var/list/sharps = list(SHARP_NONE, SHARP_EDGED, SHARP_POINTY, SHARP_NONE)
+ /// Since burn wounds need burn damage, duh
+ var/list/dam_types = list(BRUTE, BRUTE, BRUTE, BURN)
+
+ var/i = 1
+ var/list/iter_test_wound_list
+
+ for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe, /datum/wound/blunt/critical),\
+ list(/datum/wound/slash/moderate, /datum/wound/slash/severe, /datum/wound/slash/critical),\
+ list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe, /datum/wound/pierce/critical),\
+ list(/datum/wound/burn/moderate, /datum/wound/burn/severe, /datum/wound/burn/critical)))
+
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test")
+ var/datum/wound/iter_test_wound
+ var/threshold_penalty = 0
+
+ for(iter_test_wound in iter_test_wound_list)
+ var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
+ if(dam_types[i] == BRUTE)
+ tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i])
+ else if(dam_types[i] == BURN)
+ tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i])
+
+ TEST_ASSERT(length(victim.all_wounds), "Patient has no wounds when one wound is expected. Severity: [initial(iter_test_wound.severity)]")
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
+ var/datum/wound/actual_wound = victim.all_wounds[1]
+ TEST_ASSERT_EQUAL(actual_wound.type, iter_test_wound, "Patient has wound of incorrect severity. Expected: [initial(iter_test_wound.name)] Got: [actual_wound]")
+ threshold_penalty = actual_wound.threshold_penalty
+ i++
+ victim.fully_heal(TRUE) // should clear all wounds between types
+
+
+/// This test is used for making sure species with bones but no flesh (skeletons, plasmamen) can only suffer BONE_WOUNDS, and nothing tagged with FLESH_WOUND (it's possible to require both)
+/datum/unit_test/test_human_bone/Run()
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+
+ /// the limbs have no wound resistance like the chest and head do, so let's go with the r_arm
+ var/obj/item/bodypart/tested_part = victim.get_bodypart(BODY_ZONE_R_ARM)
+ /// In order of the wound types we're trying to inflict, what sharpness do we need to deal them?
+ var/list/sharps = list(SHARP_NONE, SHARP_EDGED, SHARP_POINTY, SHARP_NONE)
+ /// Since burn wounds need burn damage, duh
+ var/list/dam_types = list(BRUTE, BRUTE, BRUTE, BURN)
+
+ var/i = 1
+ var/list/iter_test_wound_list
+ victim.dna.species.species_traits &= HAS_FLESH // take away the base human's flesh (ouchie!) ((not actually ouchie, this just affects their wounds and dismemberment handling))
+
+ for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe, /datum/wound/blunt/critical),\
+ list(/datum/wound/slash/moderate, /datum/wound/slash/severe, /datum/wound/slash/critical),\
+ list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe, /datum/wound/pierce/critical),\
+ list(/datum/wound/burn/moderate, /datum/wound/burn/severe, /datum/wound/burn/critical)))
+
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test")
+ var/datum/wound/iter_test_wound
+ var/threshold_penalty = 0
+
+ for(iter_test_wound in iter_test_wound_list)
+ var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
+ if(dam_types[i] == BRUTE)
+ tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i])
+ else if(dam_types[i] == BURN)
+ tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i])
+
+ // so if we just tried to deal a flesh wound, make sure we didn't actually suffer it. We may have suffered a bone wound instead, but we just want to make sure we don't have a flesh wound
+ if(initial(iter_test_wound.wound_flags) & FLESH_WOUND)
+ if(!length(victim.all_wounds)) // not having a wound is good news
+ continue
+ else // we have to check that it's actually a bone wound and not the intended wound type
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
+ var/datum/wound/actual_wound = victim.all_wounds[1]
+ TEST_ASSERT((actual_wound.wound_flags & ~FLESH_WOUND), "Patient has flesh wound despite no HAS_FLESH flag, expected either no wound or bone wound. Offending wound: [actual_wound]")
+ threshold_penalty = actual_wound.threshold_penalty
+ else // otherwise if it's a bone wound, check that we have it per usual
+ TEST_ASSERT(length(victim.all_wounds), "Patient has no wounds when one wound is expected. Severity: [initial(iter_test_wound.severity)]")
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
+ var/datum/wound/actual_wound = victim.all_wounds[1]
+ TEST_ASSERT_EQUAL(actual_wound.type, iter_test_wound, "Patient has wound of incorrect severity. Expected: [initial(iter_test_wound.name)] Got: [actual_wound]")
+ threshold_penalty = actual_wound.threshold_penalty
+ i++
+ victim.fully_heal(TRUE) // should clear all wounds between types
diff --git a/code/modules/unit_tests/metabolizing.dm b/code/modules/unit_tests/metabolizing.dm
new file mode 100644
index 00000000000..895762c0ecc
--- /dev/null
+++ b/code/modules/unit_tests/metabolizing.dm
@@ -0,0 +1,19 @@
+/datum/unit_test/metabolization/Run()
+ // Pause natural mob life so it can be handled entirely by the test
+ SSmobs.pause()
+
+ var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/monkey/monkey = allocate(/mob/living/carbon/monkey)
+
+ for (var/reagent_type in subtypesof(/datum/reagent))
+ test_reagent(human, reagent_type)
+ test_reagent(monkey, reagent_type)
+
+/datum/unit_test/metabolization/proc/test_reagent(mob/living/carbon/C, reagent_type)
+ C.reagents.add_reagent(reagent_type, 10)
+ C.reagents.metabolize(C, can_overdose = TRUE)
+ C.reagents.clear_reagents()
+
+/datum/unit_test/metabolization/Destroy()
+ SSmobs.ignite()
+ return ..()
diff --git a/code/modules/unit_tests/say.dm b/code/modules/unit_tests/say.dm
new file mode 100644
index 00000000000..a7df5ad624b
--- /dev/null
+++ b/code/modules/unit_tests/say.dm
@@ -0,0 +1,23 @@
+/// Test to verify message mods are parsed correctly
+/datum/unit_test/get_message_mods
+ var/mob/host_mob
+
+/datum/unit_test/get_message_mods/Run()
+ host_mob = allocate(/mob/living/carbon/human)
+
+ test("Hello", "Hello", list())
+ test(";HELP", "HELP", list(MODE_HEADSET = TRUE))
+ test(";%Never gonna give you up", "Never gonna give you up", list(MODE_HEADSET = TRUE, MODE_SING = TRUE))
+ test(".s Gun plz", "Gun plz", list(RADIO_KEY = RADIO_KEY_SECURITY, RADIO_EXTENSION = RADIO_CHANNEL_SECURITY))
+ test("...What", "...What", list())
+
+/datum/unit_test/get_message_mods/proc/test(message, expected_message, list/expected_mods)
+ var/list/mods = list()
+ TEST_ASSERT_EQUAL(host_mob.get_message_mods(message, mods), expected_message, "Chopped message was not what we expected. Message: [message]")
+
+ for (var/mod_key in mods)
+ TEST_ASSERT_EQUAL(mods[mod_key], expected_mods[mod_key], "The value for [mod_key] was not what we expected. Message: [message]")
+ expected_mods -= mod_key
+
+ if (expected_mods.len)
+ Fail("Some message mods were expected, but were not returned by get_message_mods: [json_encode(expected_mods)]. Message: [message]")
diff --git a/code/modules/uplink/uplink_devices.dm b/code/modules/uplink/uplink_devices.dm
index ead123cd1f1..f57e3dd5671 100644
--- a/code/modules/uplink/uplink_devices.dm
+++ b/code/modules/uplink/uplink_devices.dm
@@ -8,6 +8,7 @@
icon = 'icons/obj/radio.dmi'
icon_state = "radio"
inhand_icon_state = "walkietalkie"
+ worn_icon_state = "radio"
desc = "A basic handheld radio that communicates with local telecommunication networks."
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 69b5b92469b..dd6b0de70bc 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -1708,8 +1708,8 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
/datum/uplink_item/role_restricted/ancient_jumpsuit
name = "Ancient Jumpsuit"
- desc = "A tattered old jumpsuit that will provide absolutely no benefit to you. It fills the wearer with a strange compulsion to blurt out 'glorf'."
- item = /obj/item/clothing/under/color/grey/glorf
+ desc = "A tattered old jumpsuit that will provide absolutely no benefit to you."
+ item = /obj/item/clothing/under/color/grey/ancient
cost = 20
restricted_roles = list("Assistant")
surplus = 0
@@ -1931,7 +1931,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
limited_stock = 1
item = /obj/item/devices/ocd_device
restricted_roles = list("Head of Personnel", "Quartermaster")
-
+
/datum/uplink_item/role_restricted/meathook
name = "Butcher's Meat Hook"
desc = "A brutal cleaver on a long chain, it allows you to pull people to your location."
@@ -1966,7 +1966,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
name = "Clown Costume"
desc = "Nothing is more terrifying than clowns with fully automatic weaponry."
item = /obj/item/storage/backpack/duffelbag/clown/syndie
-
+
/datum/uplink_item/badass/costumes/tactical_naptime
name = "Sleepy Time Pajama Bundle"
desc = "Even soldiers need to get a good nights rest. Comes with blood-red pajamas, a blankie, a hot mug of cocoa and a fuzzy friend."
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index f4fc524d0cb..fa4c2f20f77 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -318,10 +318,34 @@ GLOBAL_LIST_EMPTY(vending_products)
if(!start_empty)
R.amount = amount
R.max_amount = amount
- R.custom_price = initial(temp.custom_price)
- R.custom_premium_price = initial(temp.custom_premium_price)
+ R.custom_price = initial(temp.custom_price) * SSeconomy.inflation_value()
+ R.custom_premium_price = initial(temp.custom_premium_price) * SSeconomy.inflation_value()
R.age_restricted = initial(temp.age_restricted)
recordlist += R
+
+/**
+ * Reassign the prices of the vending machine as a result of the inflation value, as provided by SSeconomy
+ *
+ * This rebuilds both /datum/data/vending_products lists for premium and standard products based on their most relevant pricing values.
+ * Arguments:
+ * * recordlist - the list of standard product datums in the vendor to refresh their prices.
+ * * premiumlist - the list of premium product datums in the vendor to refresh their prices.
+ */
+/obj/machinery/vending/proc/reset_prices(list/recordlist, list/premiumlist)
+ for(var/R in recordlist)
+ var/datum/data/vending_product/record = R
+ var/atom/potential_product = record.product_path
+ record.custom_price = round(initial(potential_product.custom_price) * SSeconomy.inflation_value())
+ for(var/R in premiumlist)
+ var/datum/data/vending_product/record = R
+ var/atom/potential_product = record.product_path
+ var/premium_sanity = round(initial(potential_product.custom_premium_price))
+ if(premium_sanity)
+ record.custom_premium_price = round(premium_sanity * SSeconomy.inflation_value())
+ continue
+ //For some ungodly reason, some premium only items only have a custom_price
+ record.custom_premium_price = round(extra_price + (initial(potential_product.custom_price) * (SSeconomy.inflation_value() - 1)))
+
/**
* Refill a vending machine from a refill canister
*
@@ -546,8 +570,7 @@ GLOBAL_LIST_EMPTY(vending_products)
for(var/i in C.bodyparts)
var/obj/item/bodypart/squish_part = i
if(squish_part.is_organic_limb())
- //var/type_wound = pick(WOUND_LIST_BONE)
- var/type_wound = pick(list(/datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/moderate))
+ var/type_wound = pick(list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate))
squish_part.force_wound_upwards(type_wound)
else
squish_part.receive_damage(brute=30)
diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm
index 69622f13cc3..b8a0509d9fd 100644
--- a/code/modules/vending/clothesmate.dm
+++ b/code/modules/vending/clothesmate.dm
@@ -71,6 +71,7 @@
/obj/item/clothing/under/rank/captain/suit/skirt = 2,
/obj/item/clothing/under/rank/civilian/head_of_personnel/suit/skirt = 2,
/obj/item/clothing/suit/jacket = 2,
+ /obj/item/clothing/suit/hooded/wintercoat = 2,
/obj/item/clothing/suit/jacket/puffer/vest = 2,
/obj/item/clothing/suit/jacket/puffer = 2,
/obj/item/clothing/suit/jacket/letterman = 2,
@@ -100,6 +101,7 @@
/obj/item/clothing/suit/ianshirt = 1,
/obj/item/clothing/shoes/laceup = 2,
/obj/item/clothing/shoes/sandal = 2,
+ /obj/item/clothing/shoes/winterboots = 2,
/obj/item/clothing/shoes/cowboy = 2,
/obj/item/clothing/shoes/cowboy/white = 2,
/obj/item/clothing/shoes/cowboy/black = 2,
diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm
index e603af6f7f5..9f26a9589a9 100644
--- a/code/modules/vending/games.dm
+++ b/code/modules/vending/games.dm
@@ -14,7 +14,10 @@
/obj/item/camera = 3,
/obj/item/cardpack/series_one = 10,
/obj/item/cardpack/resin = 10,
- /obj/item/storage/card_binder = 10)
+ /obj/item/storage/card_binder = 10,
+ /obj/item/skillchip/basketweaving=2,
+ /obj/item/skillchip/bonsai=2,
+ /obj/item/skillchip/wine_taster=2)
contraband = list(/obj/item/dice/fudge = 9)
premium = list(/obj/item/melee/skateboard/pro = 3,
/obj/item/melee/skateboard/hoverboard = 1)
diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm
index c56ecced315..382ff5349ce 100644
--- a/code/modules/vending/medical.dm
+++ b/code/modules/vending/medical.dm
@@ -35,7 +35,7 @@
/obj/item/reagent_containers/pill/multiver = 6,
/obj/item/storage/box/gum/happiness = 3,
/obj/item/storage/box/hug/medical = 1)
- premium = list(/obj/item/reagent_containers/medigel/instabitaluri = 2,
+ premium = list(/obj/item/reagent_containers/medigel/synthflesh = 2,
/obj/item/storage/pill_bottle/psicodine = 2,
/obj/item/reagent_containers/hypospray/medipen = 3,
/obj/item/storage/belt/medical = 3,
diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm
index 1c97853c466..ce844bf7db2 100644
--- a/code/modules/zombie/items.dm
+++ b/code/modules/zombie/items.dm
@@ -12,7 +12,7 @@
var/icon_right = "bloodhand_right"
hitsound = 'sound/hallucinations/growl1.ogg'
force = 21 // Just enough to break airlocks with melee attacks
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
wound_bonus = -30
bare_wound_bonus = 15
damtype = "brute"
diff --git a/config/config.txt b/config/config.txt
index 0dba4ce4ae4..30edbecbea2 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -494,9 +494,13 @@ DEFAULT_VIEW_SQUARE 15x15
## Enable automatic profiling - Byond 513.1506 and newer only.
#AUTO_PROFILE
+## Uncomment to enable global ban DB using the provided URL. The API should expect to receive a ckey at the end of the URL.
+## More API details can be found here: https://centcom.melonmesa.com/swagger/index.html
+#CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search
+
#### DISCORD STUFFS ####
## MAKE SURE ALL SECTIONS OF THIS ARE FILLED OUT BEFORE ENABLING
-## Discord IDs can be obtained by following this guide: https://support.discordapp.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-
+## Discord IDs can be obtained by following this guide: https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-
## Uncomment to enable discord auto-roling when users link their BYOND and Discord accounts
#ENABLE_DISCORD_AUTOROLE
diff --git a/html/changelog.html b/html/changelog.html
index 3376ede8f93..66e84a36402 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -51,6 +51,472 @@
-->
+
30 July 2020
+
ATHATH updated:
+
+
You can now store a dead mouse inside of your chef's hat. Y'know, in case the living mouse you keep in there dies.
+
+
AnturK updated:
+
+
Learn new skills at your local library. Usefulness not guaranteed.
+
+
Dennok updated:
+
+
Sheetifier and Vend-a-Tray has proper named circuit boards
+
Cable hubs no more can be created by handcraft. Cuble hubs available only in cable radial menu.
+
cyborg endoskeleton now check all parts for completion
+
+
Donkie updated:
+
+
Fixed mobs/items/turfs not being predictably washed by the various methods on board
+
Fixed showers not properly reducing clothing radioactivity
+
Refactored the cleaning/washing system
+
+
Helianthus00 updated:
+
+
Deltastation: Added a new and improved permabrig
+
+
Jared-Fogle updated:
+
+
You can now only use the emergency shuttle console once per 5 seconds.
+
+
Melbert updated:
+
+
user feedback for cyborgs trying to use modules while buckled to things
+
+
Paxilmaniac updated:
+
+
a third spawn point for miners on meta so they don't spawn inside one another
+
+
TheVekter updated:
+
+
Supermatter tongs are no longer rendered invisible once you pick up a supermatter sliver.
+
+
+
29 July 2020
+
ATHATH updated:
+
+
The third law of the reporter lawset no longer obligates you to interfere with Mining and Xenobiology.
+
The fourth law of the corporate lawset no longer obligates you to depower/shut down Cargo.
+
+
Jared-Fogle updated:
+
+
Added the "Check Timer Sources" debug command to help isolate problematic cases of addtimer.
+
+
Melbert updated:
+
+
Fixes a cyborg runtime error
+
middle mouse click to cycle modules for cyborgs works properly again
+
+
Memedoktor updated:
+
+
adds positive moodlet for chaplain eating holymelon
+
+
Ryll/Shaps updated:
+
+
Non-emagged mediborgs will no longer embed people with lollipops. Emagged borgs, on the other hand..... well, they can. Fun!
+
Spears and other embeddables are now somewhat less effective at causing damage and bloodloss
+
+
ike709 and bobbahbrown updated:
+
+
Admins can now see your bans on (some) other servers.
+
+
nightred updated:
+
+
Prevents showing AI law changes after revival
+
Clown ops reinforcements spawn properly
+
+
oranges updated:
+
+
disappointment has been reworked
+
+
+
28 July 2020
+
Jared-Fogle updated:
+
+
Fixed a bug where you could not start messages with an ellipses.
+
Fixed not being able to sing over comms.
+
+
Skoglol updated:
+
+
Statues can now properly not cast spells.
+
+
Time-Green updated:
+
+
Instabitulari is called synthflesh again
+
+
+
27 July 2020
+
Ryll/Shaps updated:
+
+
Stepping on an unopened bag of chips will pop it and ruin all the chips inside
+
Added 2 unit tests for wounds
+
+
ShizCalev updated:
+
+
Fixed a minor runtime caused by ghosts pressing the lookup / lookdown keybind.
+
Supermatter slivers thrown by bombs / mass drivers will now consume a mob if they're hit by it.
+
Fixed an exploit allowing jaunting mobs to consume supermatter slivers on the ground.
+
Fixed simple animals (ie gorillas) not being consumed by supermatter slivers after picking them up.
+
Fixed an exploit allowing you to kill jaunting mobs with supermatter slivers held by tongs.
+
Objects and mobs that have move resist set to INFINITY will no longer be moved by a singularity.
+
Varediting a solar array assembly's anchored var will now correctly update its pixel offsets.
+
Smartfridges will now drop boards set to the type of fridge they were when deconstructed.
+
Fixed survival pods, black boxes, and drying racks potentially dropping basic smartfridge boards.
+
Newly constructed survival pods will no longer say that they're old and dusty.
+
Fixed "your MMI was unable to receive your mind" being sent to emagged borgs when self_destructed via a robotics console.
+
+
Wayland-Smithy updated:
+
+
The "See runechat emotes" preference now works as intended.
+
+
nightred updated:
+
+
Sheetifier now deconstructs properly
+
Fixed guardian runtime on ghost disconnect
+
+
+
26 July 2020
+
Ryll/Shaps updated:
+
+
The "Disk, Please!" achievement will now be granted to any nuclear operative who holds up any conscious non-nukie holding the disk, rather than needing the Captain. Good luck!
+
+
Skoglol updated:
+
+
Express supply console has had emagged pod amount reduced slightly and now has a cooldown.
+
*crack has a new sound effect.
+
+
Timberpoes updated:
+
+
Cybernetic Stomachs are now correctly categorised under Cybernetics in the Exosuit Fabricator.
+
+
WondaMegapon updated:
+
+
Telecommunication machines now use a TGUI based interface
+
Removed HTML based telecommunication machine code
+
Telecommunication machines can have their interfaces viewed without a multitool
+
Telecommunication machines now log changed settings
+
+
bobbahbrown updated:
+
+
Runechat now runs under its own subsystem, this should improve performance and allow for finer control over its performance.
+
+
lordpidey updated:
+
+
Arcade toy companies have found a surplus of promotional balloons from an old boyband and put them in the prize rotation. Warning: balloons may only be partially inflated, and may incite security forces to arrest you.
+
+
+
25 July 2020
+
ArcaneMusic updated:
+
+
Adds a metric ton of belt icons.
+
Paper cash, when stacked, now displays an icon scaling with it's appropriate value.
+
+
Donkie updated:
+
+
Fixed being able to cheese the voice analyzer by using multiple consecutive whitespaces
+
+
Indie-ana Jones updated:
+
+
Swarmers have changed tactics. They now utilize a swarmer beacon for spawning, and have received a number of other changes.
+
+
Jared-Fogle updated:
+
+
Fixed several edge cases where some reagents would prevent metabolization.
+
+
Ryll/Shaps updated:
+
+
Anyone with an antag datum now is shown as an antag to admin features like (?|F)
+
You can now properly repair mangy/shredded clothing with cloth
+
+
Timberpoes updated:
+
+
AI integrity restorer program now appropriately restores the integrity of AIs.
+
+
nightred updated:
+
+
Titanium does not devolve in to iron when it rusts
+
+
+
24 July 2020
+
ArcaneMusic updated:
+
+
The BEPIS has been moved to the Cargo Bay from Science, and it's prices have been adjusted to roughly late-game proportions for most players.
+
The Rolling Tables and Mauna Mug BEPIS techs have been merged into one.
+
+
EdgeLordExe updated:
+
+
Ash ascension no longer kills user.
+
+
Galdar02 updated:
+
+
Winter Boots and Coats can now be acquired from Clothesmates
+
+
Jared-Fogle updated:
+
+
You can now take off clothes while in stasis.
+
+
Maurukas updated:
+
+
All maps now receive a blood crate and surplus limb crate in Medical.
+
All maps now have a chemical locker in the Pharmacy or in Plumbing.
+
The last chemistry wardrobe locker has been removed from DeltaStation.
+
IceBox blood freezer not spawning the correct number of blood bags.
+
+
MrDoomBringer updated:
+
+
Routing protocols for contractor pods have been fixed - they should no longer send victims to mysterious crystal filled rooms
+
+
Ryll/Shaps updated:
+
+
Licking wounds on other people will automatically contract any diseases they're carrying. This is a one way transmission vector, you cannot contract disease from a felinid doctor in this way due to the partially antiseptic nature of cat saliva or whatever
+
You also cannot lick wounds if your mouth is covered or if you do not, in fact, possess a tongue
+
Your clothes and items will no longer be covered in distressingly human looking blood when bashing borgs to bits
+
Introduces piercing wounds, a new type of wound mostly related to pointy things like bullets. Piercing wounds cause bleeding that does not clot over time, as well as when that limb is hit.
+
Skeletons and plasmamen can now suffer bone wounds!
+
Dismemberment is now integrated with wounds! Simply apply a critical bleeding wound and a severe bone wound, keep whacking, and pop! Sharp weapons will cut through to bones on limbs with critical bleeding wounds, allowing you to still dismember with just a sword or axe.
+
A few projectile types, namely buckshot and shotgun slugs, have had their damage reduced slightly to account for their new wounding abilities. Buckshot may take three shots to crit now, but you can also blast peoples limbs off! Cool!
+
Health analyzers now tell you if a humanoid is missing a heart, lungs, liver, or stomach (and they need them to survive)
+
Formaldehyde replaces calomel in paramedic belts
+
Having brute damage on a limb no longer causes bleeding. Bandages now apply to all wounds on a limb, rather than be applied per-wound. Gauze also stops bleeding from dragging, but this wears out the gauze faster
+
Wounds that have their removal requirements met while the patient is on a stasis bed will now be cured of their wounds as expected. In addition, burn wounds will slowly recover while stasis'd if they have flesh regen/sanitizer applied
+
Examining a bleeding dead body now reflects that dead bodies don't actually bleed, saying their blood is pooling instead
+
Coagulant is now slightly less effective per bleeding wound for each bleed wound on a patient
+
+
Sirich96 updated:
+
+
new stunbaton sprites
+
+
YPOQ updated:
+
+
Fixed an issue that prevented throwing hats onto heads.
+
+
nightred updated:
+
+
Only living mobs can buckle to borgs
+
The golden bike horn now works after round end,
+
+
zxaber updated:
+
+
AIs disconnecting from a shell will be left looking at the shell rather than their core.
+
+
+
23 July 2020
+
ArcaneMusic updated:
+
+
Civilian bounty pads, when submitting a bounty that the department can't pay for, will allow you to submit it later.
+
Fixes 2 duplicate bounties between security/assistants.
+
+
Coul updated:
+
+
All keybinds send signals when pressed
+
+
Melbert updated:
+
+
Fixes cyborg slot cycling (and a runtime)
+
+
Nebulacrity updated:
+
+
Updated the reality fractures (Heretic influences) to have nicer sprites.
+
+
ShizCalev updated:
+
+
Fixed display cases going invisible when you put things inside of them.
+
Fixed the pedestal for open display cases being glass colored.
+
+
Skoglol updated:
+
+
electrolysis reaction now outputs 4.5u of reagents instead of 30u.
+
Removed forcesay on getting hit in classic mode.
+
+
nightred updated:
+
+
Fixed medical records for quirks.
+
Quirks settings trigger after spawn now.
+
+
tralezab updated:
+
+
More roles added for mafia!
+
Should have (outside of adminbus) way more balanced role lists in mafia now.
+
general improvements to the game have been made as well :)
+
+
+
22 July 2020
+
ArcaneMusic updated:
+
+
early plant mutations from 30+ instability
+
The Implosion Compressor now has an updated sprite.
+
The photocopier now has a 5 credit cost to use.
+
There is now a uniform component for simple payments.
+
You can now make copies of paper with the photocopier again.
+
+
Coul updated:
+
+
visible message when someone takes the item you offer them
+
cleaned up some spans and offer messages
+
+
Donkie updated:
+
+
Prevent being able to finish a reinforced girder using regular metal, making the added reinforcement disappear.
+
+
Jared-Fogle updated:
+
+
Fixed colorful reagent, red powder, and quantum hair dye breaking metabolization.
+
+
Melbert updated:
+
+
You can no longer use broken cyborg slots by being fast at clicking
+
Removed a bunch of deprecated cyborg code involving taking toxin and oxygen damage
+
Refactored part of cyborg inventory code
+
+
Ryll/Shaps updated:
+
+
Corpses buckled to chairs can no longer tackle
+
+
ShizCalev updated:
+
+
Fixed a minor runtime caused by a mob being deleted while in a coma.
+
Converted everything to use setAnchored() as opposed to setting the anchored var directly. This means that COMSIG_MOVABLE_SETANCHORED can now actually be used reliably to track if an object's anchored var has changed.
+
The refactor also means that varediting the anchored var will now properly update most items' other states that rely on it being set a certain way.
+
Fixed bookcases having the incorrect icon when they're unanchored.
+
Fixed bookcases not automatically picking up spellbooks / storage books when made/spawned.
+
Fixed bookcases not dropping spellbooks / storage books when deconstructed.
+
Fixed a runtime causing emags to not properly emag airlocks.
+
Fixed a runtime caused by a target being deleted by a gun's projectiles.
+
The light eater will now eat -even more- lights.
+
Fixed a runtime when using the peacekeeper borg's Hyperkinetic Dampener Field.
+
Player/pAI controlled MULEbots will now use power when they move.
+
Player/pAI controlled MULEbots will now turn off properly if they ran out of power while moving.
+
MULEbot's power levels now show up on the diagnostic HUD.
+
Players controlling a MULEbot will now see their power level and current load on the status panel.
+
Player controlled bots (ie securitrons, ED209's, MULEbots, ect) will now get a chat notification when they're turned on/off.
+
+
Skoglol updated:
+
+
Fixed a simplemob AI issue that prevented them from processing.
+
dead people can no longer slimelink
+
Re-added old keybinds for quick equip, set new default for suit storage slot if none was set yet.
+
Savefile version minimum bumped to 32.
+
+
Timberpoes updated:
+
+
Revenants are no longer deaf.
+
+
Winterous updated:
+
+
Changed some text for Medbots, including fixing incorrect message after updating research. Contributor's brain irreparably melted from hurdles.
+
+
bobbahbrown updated:
+
+
Improved SIGN macro performance
+
+
nightred updated:
+
+
Family heirloom moods clear on loss of the quirk.
+
Fixed the location of an Engineering sign on Meta
+
Fixed up plumbing issues in security for Meta
+
+
+
21 July 2020
+
ArcaneMusic updated:
+
+
We got hydroponics belt and suit sprites.
+
We also got engineering tool mirror sprites.
+
+
Dennok updated:
+
+
look_down verb that allow look down
+
look_up and look_down verbs don't interrupt by move, until you step under/over transparent turf.
+
+
EdgeLordExe updated:
+
+
Ash ascension spells now deal actual damage.
+
+
Fikou updated:
+
+
changes morphine description to be more accurate
+
+
Melbert updated:
+
+
Items hidden in bread, cake, and cheeses can now have unique interactions when someone bites into it them.
+
Sharp objects like shards of glass, ammunition, beakers, bottles, syringes, pills, organs, and more will now have unique interactions if you somehow manage to accidentally bite or swallow one that was hidden in food.
+
You can now insert sharp objects into food containers (bread, cake, cheese) if you're on disarm intent.
+
+
Mickyan updated:
+
+
Spraypaint can no longer be applied to any object type
+
+
RaveRadbury updated:
+
+
New insect phobia
+
+
Rohesie updated:
+
+
Emotes now show on runechat. You can disable them on your prefs.
+
+
Skoglol updated:
+
+
Arrhytmic knife now updates movespeed every time it processes, as intended.
+
Arrhytmic knife movespeed change brought more in line with other speed increasing effects.
+
Moth week start date fixed
+
Added a few new buildmode modes. Outfit, to quickly apply or remove outfits. Delete, to quickly delete any atoms. Slightly overhauled the copy proc used here and in supply pods, now compatible with most mobs.
+
+
Winterous updated:
+
+
Large water bottles (made from plastic) now have the same starting and optional transfer volumes as large beakers.
+
+
zxaber updated:
+
+
Hulk arms no longer break with punching things unintended to break hulk arms
+
Hulk arms no longer break when punching machines (but still take a small bit of recoil damage).
+
+
+
20 July 2020
+
ArcaneMusic updated:
+
+
Icebox NE Maintenance has fewer unnecessary pipes and wires, as well as less space.
+
The Civilian Bounty Pad and Console have been added to every map. Using your ID on the console will allow you to aquire a civilian bounty, which can be sent away for additional profit similar to cargo bounties.
+
Midround, the widely unstable space market can occasionally briefly completely collapse, resulting in vendor prices skyrocketing for short periods of time.
+
For admins, there are new debug items to fiddle with the economy.
+
Several new small bounties have been added to accompany the new sets of civilian bounties per job.
+
The Coin mint has been removed from the code and all maps.
+
Miasma Exports have been nerfed from 10 credits/mol to 2credits/mol when sold.
+
EXPERIMENTAL: Crew now start with higher starting balances, but lower passive income throughout the round.
+
A new economy mechanic has been added, the inflation value. In short, if player accounts hold more money than the station should probably have at a given time, the prices of vendors will go up. If vendor prices are unreasonably high, spend money with cargo or vendors.
+
Adjusted the Telepad sprite into the 2020s. sound: Telepads now make a whoopy teleport sound.
+
+
Cobby updated:
+
+
You can now tend wounds on all healable mobs if they are organic!
+
+
Fikou updated:
+
+
you can no longer cheat over the spraycanning clothes black restriction
+
+
Gamer025 updated:
+
+
You can no longer enter book or author names that are too long for the database to store
+
+
Maurukas updated:
+
+
Fix hidden pipes and unpowered devices in Icebox turbine.
+
+
ShizCalev updated:
+
+
Discord urls updated since the old one is being depreciated.
+
The client playtime panel is now sorted alphabetically as opposed to the order in which they were added to the list.
+
Defibbing someone will no longer work if their chest is covered by thick clothing. Used to just check to see if they had a spacesuit someone on their person (even if they weren't wearing it / only had it stuck to their hands.)
+
Defibbing someone will now actually require you to hold the shock paddles for the entire duration of the process. No more defibbing someone if you were disarmed during the charge time.
+
+
stylemistake updated:
+
+
tgui: Fixed a very rare case of exceptions thrown due to bugged localStorage. No more NTOS bluescreens.
+
+
tralezab updated:
+
+
landmark cleanup on mafia
+
+
19 July 2020
ArcaneMusic updated:
@@ -79,6 +545,7 @@
Timberpoes updated:
Roboticists rejoice. NanoTrasen has heard your cries for help. They've seen CCTV footage (recorded by the AI) of you all staring enviously at geneticists, lording around their shiny, polished, usable DNA Consoles. Cry no more, for you can now please your Silicon Overlords and robust those Machine Cultist Chaplains in high definition glory with a beautiful new interface added to Exosuit Fabricators.
+
Depowered and/or non-interactive tgui interfaces are now approriately non-interactive.
carshalash updated:
@@ -1710,296 +2177,6 @@
remove unnecessary var/id from /datum/material
-
-
27 May 2020
-
ArcaneMusic updated:
-
-
Icebox's mining, EVA Exits, and cargo shuttle areas have been adjusted to distinguish them from boxstation.
-
Adds Liquid Earthquake, a botany biogenerator chem that increases production speed, but increases plant weed susceptibility.
-
Adds Enduro Grow, a botany biogenerator chem that improves a plant's endurance stat, but decreases it's yield and potency.
-
-
ArcaneMusic/Azlan updated:
-
-
Updated the Ancient Spear sprite.
-
-
Kathy Ryals updated:
-
-
You can now altClick to take out a disk from a DNA Console.
-
-
Krysonism updated:
-
-
fixes the horticultural waders sprite by adding hand holes.
-
-
Kurgis updated:
-
-
New mouse detective poster.
-
-
LemonInTheDark updated:
-
-
Fixed smallscreen resizing on restart or when you die.
-
Pixel size and scaling method will properly save now.
-
-
MacBlaze1 updated:
-
-
Prevents doomsday activation while shunting and disabled doomsday if a malf shunts after activation.
-
fixed #50792, also known as the cool amazing bug that makes taking your clothes on and off duplicate an unknown onto the crew monitor program/console/device
-
-
Qustinnus updated:
-
-
You can no longer get hardcore random points by joining last second
-
-
Ryll/Shaps updated:
-
-
You can no longer shotgun canned beverages
-
Removed errant TP from the admin full monty
-
-
WarlockD updated:
-
-
Fixed the internal to use mask
-
flag change to the space heater and electrolyzer
-
-
actioninja updated:
-
-
missing textures
-
-
nemvar updated:
-
-
Scaling methods in the preference menu now have the correct names.
-
-
ohnoitsdixie updated:
-
-
Paramedics now have access to the Engineering Foyer, and no longer have to crawl into Engineering through maintenance like filthy assistants.
-
-
willox updated:
-
-
Botany grafts for traits other than 'perennial growth' are no longer non-functional
-
Cross-pollination can no longer result in plants entering an infinitely-harvestable state
-
-
zxaber updated:
-
-
Two new apps for modular computers are available: Lifeline for Medical, and Fission360 for anyone with access to the Syndicate repository. Lifeline is an improved suit sensors tracker, and Fission360 is the same but for nuclear-related things.
-
-
-
26 May 2020
-
Fikou, spookydonut made the glove code sane updated:
-
-
concussive gauntlets! available at your local tendril chest
-
tendril loot has changed a bit
-
hulks can now break rocks
-
-
JoshAdamPowell updated:
-
-
Simplified granibitaluri recipe
-
-
MrDoomBringer updated:
-
-
Reaching legendary levels of any given skill will grant you an otherwise unobtainable reward.
-
Nanotrasen has recently acquired new software to track employee skill development, straight from your PDA! Check it out! In unrelated news, the rate of summary executions handed out to Nanotrasen's lowest performing employees has risen sharply.
-
Modify player's skills with the new Skill Panel menu, available from the Player Panel!
-
-
Paxilmaniac updated:
-
-
things to the bounty hunter ship that might be important, such as oxygen tanks, and a toolbox, along with two tiny fans in the airlocks
-
-
Pumpkinoe updated:
-
-
Added a new large emergency shuttle for purchase at 8000 Cr, the Dynamic Enviornment Interaction Shuttle. Try it out today and frolic with the monkeys!
-
-
Ryll/Shaps updated:
-
-
Sleeping carp no longer grants outright damage resistances to all forms of damage. Its price has been decreased to 12TC
-
-
TerraGS updated:
-
-
Material containers are better at managing local storage
-
-
Timberpoes updated:
-
-
SMES units now self-charge when containing yellow slime cores.
-
-
Tlaltecuhtli updated:
-
-
janibelt/medibelt can be opened from backpack
-
removed some defunct code (picking a color of cable but its all yellow in belt.dm)
-
bandoliers now hold their intended 18 shells.
-
-
WarlockD updated:
-
-
Swapped a few arguments out of the materials container
-
-
kevinz000 updated:
-
-
Hierophant now uses its ranged abilities in melee attacks, meaning the strategy of standing still and tanking the hits is no longer viable in 99% of circumstances.
-
-
-
25 May 2020
-
Cyberboss updated:
-
-
The `notify` TGS command will no longer work if the related config setting is disabled
-
Fixed not actually limiting chat sends to tagged channels as the config should allow when running under TGS4.
-
Fixed the `notify` command not working for IRC users.
-
-
Fikou updated:
-
-
fixed science glasses recipe using beer goggles instead of science glasses
-
-
Gamer025 updated:
-
-
Nanotrasen checked the last few expenses reports and decided to reduce payday spending. Because of this, the salary of all non human employees has been reduced by 25%. Please consult your local Head of Personal for more information.
-
-
Kelenius updated:
-
-
Cyborgs (except Peacekeeper and Standard) now have access to their department's radio.
-
-
LemonInTheDark updated:
-
-
Changed the way h2o interacts with the engine. It should acts similar to o2 normally, but in high concentrations it cools the engine down. Have fun :)
-
Mulebots no longer go insane when shit starts to lag
-
Refactored client.view into a datum, changed some zoom values to work properly
-
Spinning while using a binocular will work as expected
-
Fully adds widescreen support to zooming
-
I've moved the pixel scaling and zoom methods from the menu bar to the game preferences window. You'll need to reset them.
-
Pixel size will now automatically update on zoom, so you won't end up with ui buttons you can't see.
-
FIxed the epic gamer skill of binocular applied to advanced camera console
-
-
LemonInTheDark, with thanks to ArcaneDefence for the pointer updated:
-
-
Scrubbers no longer cool pipenets attached to them when scrubbing space
-
-
MarioWizard119 updated:
-
-
Synthflesh now also works via vapor.
-
-
NewSta updated:
-
-
You might potentially see better a/an usage
-
-
Ryll/Shaps updated:
-
-
You can now use ! and ? at the end of 'spawn' chatbar verb commands to search for only the ends of paths, or to automatically select a random type of the paths your search returns! Happy badminning!
-
Adds a config option for OOC kindness commendations! When enabled, a small percentage of the crew gets asked if anyone made their round better, and those people will get a little heart next to their name in OOC for 24h!
-
lobby menu work again
-
-
Shadark updated:
-
-
Manually printed barcodes are now working correctly.
-
-
TetraK1 updated:
-
-
Ghosts can no longer control atmos stuff with alt/ctrl click.
-
Added logging to ctrl/alt clicks on atmos stuff
-
-
TheVekter updated:
-
-
Nanotrasen's export price for Hydrogen no longer violates the basic rules of supply and demand.
-
-
actioninja updated:
-
-
smon,k weed
-
-
antropod updated:
-
-
Fix PanDEMIC 2200 not showing Blood DNA and Blood Type
-
-
kevinz000 updated:
-
-
dir_inverse_multiz() should work now. should.
-
-
nemvar updated:
-
-
The camera console looks fancier now.
-
-
qustinnus updated:
-
-
you can no longer move up when you are going to fall down anyways, or you know, dead.
-
simple multi-z audio
-
-
spessman-007 updated:
-
-
Quite a few misspellings have been corrected
-
-
tralezab updated:
-
-
anomaly crystal theme warping for the jungle now uses wooden walls instead of sandstone ones
-
-
wesoda25 updated:
-
-
The progress bar for butchering large mobs is no longer offset.
-
-
zxaber updated:
-
-
Explosions that were able to destroy doors before will now rip them from the universe.
-
-
-
24 May 2020
-
Cobby updated:
-
-
Dissection will now give XP instead of research points.
-
-
Fikou updated:
-
-
admins can now do html in ahelps properly
-
-
qustinnus updated:
-
-
Fixes you being able to put in more than one license plate in the license plate machine
-
-
-
22 May 2020
-
ArcaneMusic updated:
-
-
Adds a new assembly component, the freezer, for gradually making the environment colder.
-
-
Dorsidwarf updated:
-
-
Swarmer structures are no longer broken.
-
-
Fikou updated:
-
-
you can no longer inject html in ahelps
-
you cant either, jannies
-
-
Ghilker updated:
-
-
Cryotubes can no longer reach fusion level heat
-
Cryotubes breakdown if the heat inside is higher than 2000 K
-
Cryotubes extinguish the mob if on fire
-
-
Mickyan updated:
-
-
Fixed missing icon on wigs
-
-
Whoneedspacee updated:
-
-
Icebox Station has been added, explore the depths of the Ice Moon with boxstation right on top of it, don't forget your coat and internals!
-
You can now mine rock flora into volcanic ash to make glass.
-
-
kevinz000 updated:
-
-
Circlegame items now properly delete on drop and can't be made with full hands.
-
-
wesoda25 updated:
-
-
fixes a misspelled xenobiology sign
-
-
willox updated:
-
-
diseases now retain the ability to infect people after having 'inorganic biology' or 'necrotic metabolism' removed from them
-
-
-
20 May 2020
-
Skoglol updated:
-
-
Another dynamic rolling restricted roles as antags fix.
-
-
Thebleh updated:
-
-
Fixes MetaStation's disposals
-
-
dootdoom updated:
-
-
Straight Jacket now has bigger bolder red text to alert you someone is putting it on you.
-
GoonStation 13 Development Team
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index 0ffd719abf6..bf373072daf 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -40912,5 +40912,390 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
lording around their shiny, polished, usable DNA Consoles. Cry no more, for
you can now please your Silicon Overlords and robust those Machine Cultist Chaplains
in high definition glory with a beautiful new interface added to Exosuit Fabricators.
+ - bugfix: Depowered and/or non-interactive tgui interfaces are now approriately
+ non-interactive.
carshalash:
- tweak: Reintroduces module type to AI shell name
+2020-07-20:
+ ArcaneMusic:
+ - bugfix: Icebox NE Maintenance has fewer unnecessary pipes and wires, as well as
+ less space.
+ - rscadd: The Civilian Bounty Pad and Console have been added to every map. Using
+ your ID on the console will allow you to aquire a civilian bounty, which can
+ be sent away for additional profit similar to cargo bounties.
+ - rscadd: Midround, the widely unstable space market can occasionally briefly completely
+ collapse, resulting in vendor prices skyrocketing for short periods of time.
+ - rscadd: For admins, there are new debug items to fiddle with the economy.
+ - rscadd: Several new small bounties have been added to accompany the new sets of
+ civilian bounties per job.
+ - rscdel: The Coin mint has been removed from the code and all maps.
+ - tweak: Miasma Exports have been nerfed from 10 credits/mol to 2credits/mol when
+ sold.
+ - balance: 'EXPERIMENTAL: Crew now start with higher starting balances, but lower
+ passive income throughout the round.'
+ - balance: A new economy mechanic has been added, the inflation value. In short,
+ if player accounts hold more money than the station should probably have at
+ a given time, the prices of vendors will go up. If vendor prices are unreasonably
+ high, spend money with cargo or vendors.
+ - imageadd: 'Adjusted the Telepad sprite into the 2020s. sound: Telepads now make
+ a whoopy teleport sound.'
+ Cobby:
+ - balance: You can now tend wounds on all healable mobs if they are organic!
+ Fikou:
+ - balance: you can no longer cheat over the spraycanning clothes black restriction
+ Gamer025:
+ - bugfix: You can no longer enter book or author names that are too long for the
+ database to store
+ Maurukas:
+ - bugfix: Fix hidden pipes and unpowered devices in Icebox turbine.
+ ShizCalev:
+ - spellcheck: Discord urls updated since the old one is being depreciated.
+ - admin: The client playtime panel is now sorted alphabetically as opposed to the
+ order in which they were added to the list.
+ - bugfix: Defibbing someone will no longer work if their chest is covered by thick
+ clothing. Used to just check to see if they had a spacesuit someone on their
+ person (even if they weren't wearing it / only had it stuck to their hands.)
+ - bugfix: Defibbing someone will now actually require you to hold the shock paddles
+ for the entire duration of the process. No more defibbing someone if you were
+ disarmed during the charge time.
+ stylemistake:
+ - bugfix: 'tgui: Fixed a very rare case of exceptions thrown due to bugged localStorage.
+ No more NTOS bluescreens.'
+ tralezab:
+ - bugfix: landmark cleanup on mafia
+2020-07-21:
+ ArcaneMusic:
+ - imageadd: We got hydroponics belt and suit sprites.
+ - imageadd: We also got engineering tool mirror sprites.
+ Dennok:
+ - rscadd: look_down verb that allow look down
+ - tweak: look_up and look_down verbs don't interrupt by move, until you step under/over
+ transparent turf.
+ EdgeLordExe:
+ - balance: Ash ascension spells now deal actual damage.
+ Fikou:
+ - spellcheck: changes morphine description to be more accurate
+ Melbert:
+ - rscadd: Items hidden in bread, cake, and cheeses can now have unique interactions
+ when someone bites into it them.
+ - rscadd: Sharp objects like shards of glass, ammunition, beakers, bottles, syringes,
+ pills, organs, and more will now have unique interactions if you somehow manage
+ to accidentally bite or swallow one that was hidden in food.
+ - tweak: You can now insert sharp objects into food containers (bread, cake, cheese)
+ if you're on disarm intent.
+ Mickyan:
+ - rscdel: Spraypaint can no longer be applied to any object type
+ RaveRadbury:
+ - tweak: New insect phobia
+ Rohesie:
+ - rscadd: Emotes now show on runechat. You can disable them on your prefs.
+ Skoglol:
+ - bugfix: Arrhytmic knife now updates movespeed every time it processes, as intended.
+ - balance: Arrhytmic knife movespeed change brought more in line with other speed
+ increasing effects.
+ - bugfix: Moth week start date fixed
+ - admin: Added a few new buildmode modes. Outfit, to quickly apply or remove outfits.
+ Delete, to quickly delete any atoms. Slightly overhauled the copy proc used
+ here and in supply pods, now compatible with most mobs.
+ Winterous:
+ - tweak: Large water bottles (made from plastic) now have the same starting and
+ optional transfer volumes as large beakers.
+ zxaber:
+ - bugfix: Hulk arms no longer break with punching things unintended to break hulk
+ arms
+ - balance: Hulk arms no longer break when punching machines (but still take a small
+ bit of recoil damage).
+2020-07-22:
+ ArcaneMusic:
+ - bugfix: early plant mutations from 30+ instability
+ - imageadd: The Implosion Compressor now has an updated sprite.
+ - tweak: The photocopier now has a 5 credit cost to use.
+ - code_imp: There is now a uniform component for simple payments.
+ - bugfix: You can now make copies of paper with the photocopier again.
+ Coul:
+ - rscadd: visible message when someone takes the item you offer them
+ - spellcheck: cleaned up some spans and offer messages
+ Donkie:
+ - tweak: Prevent being able to finish a reinforced girder using regular metal, making
+ the added reinforcement disappear.
+ Jared-Fogle:
+ - bugfix: Fixed colorful reagent, red powder, and quantum hair dye breaking metabolization.
+ Melbert:
+ - bugfix: You can no longer use broken cyborg slots by being fast at clicking
+ - code_imp: Removed a bunch of deprecated cyborg code involving taking toxin and
+ oxygen damage
+ - refactor: Refactored part of cyborg inventory code
+ Ryll/Shaps:
+ - bugfix: Corpses buckled to chairs can no longer tackle
+ ShizCalev:
+ - bugfix: Fixed a minor runtime caused by a mob being deleted while in a coma.
+ - refactor: Converted everything to use setAnchored() as opposed to setting the
+ anchored var directly. This means that COMSIG_MOVABLE_SETANCHORED can now actually
+ be used reliably to track if an object's anchored var has changed.
+ - bugfix: The refactor also means that varediting the anchored var will now properly
+ update most items' other states that rely on it being set a certain way.
+ - bugfix: Fixed bookcases having the incorrect icon when they're unanchored.
+ - bugfix: Fixed bookcases not automatically picking up spellbooks / storage books
+ when made/spawned.
+ - bugfix: Fixed bookcases not dropping spellbooks / storage books when deconstructed.
+ - bugfix: Fixed a runtime causing emags to not properly emag airlocks.
+ - bugfix: Fixed a runtime caused by a target being deleted by a gun's projectiles.
+ - bugfix: The light eater will now eat -even more- lights.
+ - bugfix: Fixed a runtime when using the peacekeeper borg's Hyperkinetic Dampener
+ Field.
+ - bugfix: Player/pAI controlled MULEbots will now use power when they move.
+ - bugfix: Player/pAI controlled MULEbots will now turn off properly if they ran
+ out of power while moving.
+ - tweak: MULEbot's power levels now show up on the diagnostic HUD.
+ - tweak: Players controlling a MULEbot will now see their power level and current
+ load on the status panel.
+ - bugfix: Player controlled bots (ie securitrons, ED209's, MULEbots, ect) will now
+ get a chat notification when they're turned on/off.
+ Skoglol:
+ - bugfix: Fixed a simplemob AI issue that prevented them from processing.
+ - bugfix: dead people can no longer slimelink
+ - bugfix: Re-added old keybinds for quick equip, set new default for suit storage
+ slot if none was set yet.
+ - code_imp: Savefile version minimum bumped to 32.
+ Timberpoes:
+ - bugfix: Revenants are no longer deaf.
+ Winterous:
+ - tweak: Changed some text for Medbots, including fixing incorrect message after
+ updating research. Contributor's brain irreparably melted from hurdles.
+ bobbahbrown:
+ - refactor: Improved SIGN macro performance
+ nightred:
+ - bugfix: Family heirloom moods clear on loss of the quirk.
+ - bugfix: Fixed the location of an Engineering sign on Meta
+ - bugfix: Fixed up plumbing issues in security for Meta
+2020-07-23:
+ ArcaneMusic:
+ - bugfix: Civilian bounty pads, when submitting a bounty that the department can't
+ pay for, will allow you to submit it later.
+ - bugfix: Fixes 2 duplicate bounties between security/assistants.
+ Coul:
+ - code_imp: All keybinds send signals when pressed
+ Melbert:
+ - bugfix: Fixes cyborg slot cycling (and a runtime)
+ Nebulacrity:
+ - imageadd: Updated the reality fractures (Heretic influences) to have nicer sprites.
+ ShizCalev:
+ - bugfix: Fixed display cases going invisible when you put things inside of them.
+ - bugfix: Fixed the pedestal for open display cases being glass colored.
+ Skoglol:
+ - balance: electrolysis reaction now outputs 4.5u of reagents instead of 30u.
+ - rscdel: Removed forcesay on getting hit in classic mode.
+ nightred:
+ - bugfix: Fixed medical records for quirks.
+ - bugfix: Quirks settings trigger after spawn now.
+ tralezab:
+ - rscadd: More roles added for mafia!
+ - balance: Should have (outside of adminbus) way more balanced role lists in mafia
+ now.
+ - bugfix: general improvements to the game have been made as well :)
+2020-07-24:
+ ArcaneMusic:
+ - tweak: The BEPIS has been moved to the Cargo Bay from Science, and it's prices
+ have been adjusted to roughly late-game proportions for most players.
+ - balance: The Rolling Tables and Mauna Mug BEPIS techs have been merged into one.
+ EdgeLordExe:
+ - bugfix: Ash ascension no longer kills user.
+ Galdar02:
+ - rscadd: Winter Boots and Coats can now be acquired from Clothesmates
+ Jared-Fogle:
+ - rscadd: You can now take off clothes while in stasis.
+ Maurukas:
+ - tweak: All maps now receive a blood crate and surplus limb crate in Medical.
+ - tweak: All maps now have a chemical locker in the Pharmacy or in Plumbing.
+ - rscdel: The last chemistry wardrobe locker has been removed from DeltaStation.
+ - bugfix: IceBox blood freezer not spawning the correct number of blood bags.
+ MrDoomBringer:
+ - bugfix: Routing protocols for contractor pods have been fixed - they should no
+ longer send victims to mysterious crystal filled rooms
+ Ryll/Shaps:
+ - tweak: Licking wounds on other people will automatically contract any diseases
+ they're carrying. This is a one way transmission vector, you cannot contract
+ disease from a felinid doctor in this way due to the partially antiseptic nature
+ of cat saliva or whatever
+ - tweak: You also cannot lick wounds if your mouth is covered or if you do not,
+ in fact, possess a tongue
+ - bugfix: Your clothes and items will no longer be covered in distressingly human
+ looking blood when bashing borgs to bits
+ - rscadd: Introduces piercing wounds, a new type of wound mostly related to pointy
+ things like bullets. Piercing wounds cause bleeding that does not clot over
+ time, as well as when that limb is hit.
+ - rscadd: Skeletons and plasmamen can now suffer bone wounds!
+ - tweak: Dismemberment is now integrated with wounds! Simply apply a critical bleeding
+ wound and a severe bone wound, keep whacking, and pop! Sharp weapons will cut
+ through to bones on limbs with critical bleeding wounds, allowing you to still
+ dismember with just a sword or axe.
+ - tweak: A few projectile types, namely buckshot and shotgun slugs, have had their
+ damage reduced slightly to account for their new wounding abilities. Buckshot
+ may take three shots to crit now, but you can also blast peoples limbs off!
+ Cool!
+ - rscadd: Health analyzers now tell you if a humanoid is missing a heart, lungs,
+ liver, or stomach (and they need them to survive)
+ - tweak: Formaldehyde replaces calomel in paramedic belts
+ - bugfix: Having brute damage on a limb no longer causes bleeding. Bandages now
+ apply to all wounds on a limb, rather than be applied per-wound. Gauze also
+ stops bleeding from dragging, but this wears out the gauze faster
+ - tweak: Wounds that have their removal requirements met while the patient is on
+ a stasis bed will now be cured of their wounds as expected. In addition, burn
+ wounds will slowly recover while stasis'd if they have flesh regen/sanitizer
+ applied
+ - bugfix: Examining a bleeding dead body now reflects that dead bodies don't actually
+ bleed, saying their blood is pooling instead
+ - tweak: Coagulant is now slightly less effective per bleeding wound for each bleed
+ wound on a patient
+ Sirich96:
+ - imageadd: new stunbaton sprites
+ YPOQ:
+ - bugfix: Fixed an issue that prevented throwing hats onto heads.
+ nightred:
+ - bugfix: Only living mobs can buckle to borgs
+ - bugfix: The golden bike horn now works after round end,
+ zxaber:
+ - tweak: AIs disconnecting from a shell will be left looking at the shell rather
+ than their core.
+2020-07-25:
+ ArcaneMusic:
+ - imageadd: Adds a metric ton of belt icons.
+ - bugfix: Paper cash, when stacked, now displays an icon scaling with it's appropriate
+ value.
+ Donkie:
+ - bugfix: Fixed being able to cheese the voice analyzer by using multiple consecutive
+ whitespaces
+ Indie-ana Jones:
+ - balance: Swarmers have changed tactics. They now utilize a swarmer beacon for
+ spawning, and have received a number of other changes.
+ Jared-Fogle:
+ - bugfix: Fixed several edge cases where some reagents would prevent metabolization.
+ Ryll/Shaps:
+ - bugfix: Anyone with an antag datum now is shown as an antag to admin features
+ like (?|F)
+ - bugfix: You can now properly repair mangy/shredded clothing with cloth
+ Timberpoes:
+ - bugfix: AI integrity restorer program now appropriately restores the integrity
+ of AIs.
+ nightred:
+ - bugfix: Titanium does not devolve in to iron when it rusts
+2020-07-26:
+ Ryll/Shaps:
+ - tweak: The "Disk, Please!" achievement will now be granted to any nuclear operative
+ who holds up any conscious non-nukie holding the disk, rather than needing the
+ Captain. Good luck!
+ Skoglol:
+ - tweak: Express supply console has had emagged pod amount reduced slightly and
+ now has a cooldown.
+ - soundadd: '*crack has a new sound effect.'
+ Timberpoes:
+ - bugfix: Cybernetic Stomachs are now correctly categorised under Cybernetics in
+ the Exosuit Fabricator.
+ WondaMegapon:
+ - rscadd: Telecommunication machines now use a TGUI based interface
+ - rscdel: Removed HTML based telecommunication machine code
+ - balance: Telecommunication machines can have their interfaces viewed without a
+ multitool
+ - server: Telecommunication machines now log changed settings
+ bobbahbrown:
+ - rscadd: Runechat now runs under its own subsystem, this should improve performance
+ and allow for finer control over its performance.
+ lordpidey:
+ - rscadd: 'Arcade toy companies have found a surplus of promotional balloons from
+ an old boyband and put them in the prize rotation. Warning: balloons may only
+ be partially inflated, and may incite security forces to arrest you.'
+2020-07-27:
+ Ryll/Shaps:
+ - rscadd: Stepping on an unopened bag of chips will pop it and ruin all the chips
+ inside
+ - code_imp: Added 2 unit tests for wounds
+ ShizCalev:
+ - bugfix: Fixed a minor runtime caused by ghosts pressing the lookup / lookdown
+ keybind.
+ - bugfix: Supermatter slivers thrown by bombs / mass drivers will now consume a
+ mob if they're hit by it.
+ - bugfix: Fixed an exploit allowing jaunting mobs to consume supermatter slivers
+ on the ground.
+ - bugfix: Fixed simple animals (ie gorillas) not being consumed by supermatter slivers
+ after picking them up.
+ - bugfix: Fixed an exploit allowing you to kill jaunting mobs with supermatter slivers
+ held by tongs.
+ - bugfix: Objects and mobs that have move resist set to INFINITY will no longer
+ be moved by a singularity.
+ - bugfix: Varediting a solar array assembly's anchored var will now correctly update
+ its pixel offsets.
+ - bugfix: Smartfridges will now drop boards set to the type of fridge they were
+ when deconstructed.
+ - bugfix: Fixed survival pods, black boxes, and drying racks potentially dropping
+ basic smartfridge boards.
+ - bugfix: Newly constructed survival pods will no longer say that they're old and
+ dusty.
+ - bugfix: Fixed "your MMI was unable to receive your mind" being sent to emagged
+ borgs when self_destructed via a robotics console.
+ Wayland-Smithy:
+ - bugfix: The "See runechat emotes" preference now works as intended.
+ nightred:
+ - bugfix: Sheetifier now deconstructs properly
+ - bugfix: Fixed guardian runtime on ghost disconnect
+2020-07-28:
+ Jared-Fogle:
+ - bugfix: Fixed a bug where you could not start messages with an ellipses.
+ - bugfix: Fixed not being able to sing over comms.
+ Skoglol:
+ - bugfix: Statues can now properly not cast spells.
+ Time-Green:
+ - tweak: Instabitulari is called synthflesh again
+2020-07-29:
+ ATHATH:
+ - tweak: The third law of the reporter lawset no longer obligates you to interfere
+ with Mining and Xenobiology.
+ - tweak: The fourth law of the corporate lawset no longer obligates you to depower/shut
+ down Cargo.
+ Jared-Fogle:
+ - admin: Added the "Check Timer Sources" debug command to help isolate problematic
+ cases of addtimer.
+ Melbert:
+ - bugfix: Fixes a cyborg runtime error
+ - bugfix: middle mouse click to cycle modules for cyborgs works properly again
+ Memedoktor:
+ - tweak: adds positive moodlet for chaplain eating holymelon
+ Ryll/Shaps:
+ - bugfix: Non-emagged mediborgs will no longer embed people with lollipops. Emagged
+ borgs, on the other hand..... well, they can. Fun!
+ - balance: Spears and other embeddables are now somewhat less effective at causing
+ damage and bloodloss
+ ike709 and bobbahbrown:
+ - rscadd: Admins can now see your bans on (some) other servers.
+ nightred:
+ - bugfix: Prevents showing AI law changes after revival
+ - bugfix: Clown ops reinforcements spawn properly
+ oranges:
+ - soundadd: disappointment has been reworked
+2020-07-30:
+ ATHATH:
+ - rscadd: You can now store a dead mouse inside of your chef's hat. Y'know, in case
+ the living mouse you keep in there dies.
+ AnturK:
+ - rscadd: Learn new skills at your local library. Usefulness not guaranteed.
+ Dennok:
+ - bugfix: Sheetifier and Vend-a-Tray has proper named circuit boards
+ - bugfix: Cable hubs no more can be created by handcraft. Cuble hubs available only
+ in cable radial menu.
+ - bugfix: cyborg endoskeleton now check all parts for completion
+ Donkie:
+ - bugfix: Fixed mobs/items/turfs not being predictably washed by the various methods
+ on board
+ - bugfix: Fixed showers not properly reducing clothing radioactivity
+ - refactor: Refactored the cleaning/washing system
+ Helianthus00:
+ - rscadd: 'Deltastation: Added a new and improved permabrig'
+ Jared-Fogle:
+ - tweak: You can now only use the emergency shuttle console once per 5 seconds.
+ Melbert:
+ - tweak: user feedback for cyborgs trying to use modules while buckled to things
+ Paxilmaniac:
+ - rscadd: a third spawn point for miners on meta so they don't spawn inside one
+ another
+ TheVekter:
+ - bugfix: Supermatter tongs are no longer rendered invisible once you pick up a
+ supermatter sliver.
diff --git a/html/changelogs/AutoChangeLog-pr-52421.yml b/html/changelogs/AutoChangeLog-pr-52421.yml
new file mode 100644
index 00000000000..2aed3b503d6
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-52421.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - rscadd: "Recent reports have seen increased rates of infection from reused glasses. Cleaning them with soap or rags drastically decreases chance of infection."
diff --git a/icons/UI_Icons/chat/chat_icons.dmi b/icons/UI_Icons/chat/chat_icons.dmi
index 7040b3966f0..8cc4b2c5598 100644
Binary files a/icons/UI_Icons/chat/chat_icons.dmi and b/icons/UI_Icons/chat/chat_icons.dmi differ
diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi
index 61e67a971b1..0e0522b143c 100644
Binary files a/icons/effects/eldritch.dmi and b/icons/effects/eldritch.dmi differ
diff --git a/icons/misc/buildmode.dmi b/icons/misc/buildmode.dmi
index f0de428b6c5..fb51ad68116 100644
Binary files a/icons/misc/buildmode.dmi and b/icons/misc/buildmode.dmi differ
diff --git a/icons/mob/clothing/belt.dmi b/icons/mob/clothing/belt.dmi
index 0f0afb12639..482da90547f 100644
Binary files a/icons/mob/clothing/belt.dmi and b/icons/mob/clothing/belt.dmi differ
diff --git a/icons/mob/clothing/belt_mirror.dmi b/icons/mob/clothing/belt_mirror.dmi
index f054fdebb90..924aaf05259 100644
Binary files a/icons/mob/clothing/belt_mirror.dmi and b/icons/mob/clothing/belt_mirror.dmi differ
diff --git a/icons/mob/easter.dmi b/icons/mob/easter.dmi
index 300f418c7ab..2cd67d5112d 100644
Binary files a/icons/mob/easter.dmi and b/icons/mob/easter.dmi differ
diff --git a/icons/mob/inhands/balloons_lefthand.dmi b/icons/mob/inhands/balloons_lefthand.dmi
index 0e9eda3194c..cf51ac313e2 100644
Binary files a/icons/mob/inhands/balloons_lefthand.dmi and b/icons/mob/inhands/balloons_lefthand.dmi differ
diff --git a/icons/mob/inhands/balloons_righthand.dmi b/icons/mob/inhands/balloons_righthand.dmi
index e42884d0481..606f2007fd7 100644
Binary files a/icons/mob/inhands/balloons_righthand.dmi and b/icons/mob/inhands/balloons_righthand.dmi differ
diff --git a/icons/mob/screen_cyborg.dmi b/icons/mob/screen_cyborg.dmi
index 2f87e9796f1..c0c112bdeb9 100644
Binary files a/icons/mob/screen_cyborg.dmi and b/icons/mob/screen_cyborg.dmi differ
diff --git a/icons/obj/balloons.dmi b/icons/obj/balloons.dmi
index 0019c732e2f..0ed6f471bfb 100644
Binary files a/icons/obj/balloons.dmi and b/icons/obj/balloons.dmi differ
diff --git a/icons/obj/computer.dmi b/icons/obj/computer.dmi
index 7b5061d5431..8d2d8190ead 100644
Binary files a/icons/obj/computer.dmi and b/icons/obj/computer.dmi differ
diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi
index 4e59fd1b61b..6dde67d6683 100644
Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ
diff --git a/icons/obj/machines/research.dmi b/icons/obj/machines/research.dmi
index f31169996e4..12780e758ff 100644
Binary files a/icons/obj/machines/research.dmi and b/icons/obj/machines/research.dmi differ
diff --git a/icons/obj/stationobjs.dmi b/icons/obj/stationobjs.dmi
index 7cedd608b7e..5b59e357d78 100644
Binary files a/icons/obj/stationobjs.dmi and b/icons/obj/stationobjs.dmi differ
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index 672d8fca47d..c9e5bc31fe5 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ
diff --git a/icons/obj/telescience.dmi b/icons/obj/telescience.dmi
index 1e60c01131f..212981aa0ba 100644
Binary files a/icons/obj/telescience.dmi and b/icons/obj/telescience.dmi differ
diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm
index ef4f35b56be..50c644f01cd 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -61,7 +61,10 @@ h1.alert, h2.alert {color: #000000;}
.emote { font-style: italic;}
.userdanger {color: #ff0000; font-weight: bold; font-size: 3;}
+.danger {color: #ff0000; font-weight: bold;}
.danger {color: #ff0000;}
+.tinydanger {color: #ff0000; font-size: 85%;}
+.smalldanger {color: #ff0000; font-size: 90%;}
.warning {color: #ff0000; font-style: italic;}
.boldwarning {color: #ff0000; font-style: italic; font-weight: bold}
.announce {color: #228b22; font-weight: bold;}
@@ -70,6 +73,9 @@ h1.alert, h2.alert {color: #000000;}
.rose {color: #ff5050;}
.info {color: #0000CC;}
.notice {color: #000099;}
+.tinynotice {color: #000099; font-size: 85%;}
+.smallnotice {color: #000099; font-size: 90%;}
+.smallnoticeital {color: #000099; font-style: italic; font-size: 90%;}
.boldnotice {color: #000099; font-weight: bold;}
.hear {color: #000099; font-style: italic;}
.adminnotice {color: #0000ff;}
@@ -87,6 +93,7 @@ h1.alert, h2.alert {color: #000000;}
.hierophant_warning {color: #660099; font-style: italic;}
.purple {color: #5e2d79;}
.holoparasite {color: #35333a;}
+.bounty {color: #ab6613; font-style: italic;}
.revennotice {color: #1d2953;}
.revenboldnotice {color: #1d2953; font-weight: bold;}
diff --git a/sound/effects/chipbagpop.ogg b/sound/effects/chipbagpop.ogg
new file mode 100644
index 00000000000..9f975ab6d24
Binary files /dev/null and b/sound/effects/chipbagpop.ogg differ
diff --git a/sound/effects/blood1.ogg b/sound/effects/wounds/blood1.ogg
similarity index 100%
rename from sound/effects/blood1.ogg
rename to sound/effects/wounds/blood1.ogg
diff --git a/sound/effects/blood2.ogg b/sound/effects/wounds/blood2.ogg
similarity index 100%
rename from sound/effects/blood2.ogg
rename to sound/effects/wounds/blood2.ogg
diff --git a/sound/effects/blood3.ogg b/sound/effects/wounds/blood3.ogg
similarity index 100%
rename from sound/effects/blood3.ogg
rename to sound/effects/wounds/blood3.ogg
diff --git a/sound/effects/crack1.ogg b/sound/effects/wounds/crack1.ogg
similarity index 100%
rename from sound/effects/crack1.ogg
rename to sound/effects/wounds/crack1.ogg
diff --git a/sound/effects/crack2.ogg b/sound/effects/wounds/crack2.ogg
similarity index 100%
rename from sound/effects/crack2.ogg
rename to sound/effects/wounds/crack2.ogg
diff --git a/sound/effects/wounds/crackandbleed.ogg b/sound/effects/wounds/crackandbleed.ogg
new file mode 100644
index 00000000000..ea07f13d482
Binary files /dev/null and b/sound/effects/wounds/crackandbleed.ogg differ
diff --git a/sound/effects/wounds/pierce1.ogg b/sound/effects/wounds/pierce1.ogg
new file mode 100644
index 00000000000..cd7b7c39610
Binary files /dev/null and b/sound/effects/wounds/pierce1.ogg differ
diff --git a/sound/effects/wounds/pierce2.ogg b/sound/effects/wounds/pierce2.ogg
new file mode 100644
index 00000000000..4977cab299f
Binary files /dev/null and b/sound/effects/wounds/pierce2.ogg differ
diff --git a/sound/effects/wounds/pierce3.ogg b/sound/effects/wounds/pierce3.ogg
new file mode 100644
index 00000000000..e81700b1348
Binary files /dev/null and b/sound/effects/wounds/pierce3.ogg differ
diff --git a/sound/effects/sizzle1.ogg b/sound/effects/wounds/sizzle1.ogg
similarity index 100%
rename from sound/effects/sizzle1.ogg
rename to sound/effects/wounds/sizzle1.ogg
diff --git a/sound/effects/sizzle2.ogg b/sound/effects/wounds/sizzle2.ogg
similarity index 100%
rename from sound/effects/sizzle2.ogg
rename to sound/effects/wounds/sizzle2.ogg
diff --git a/sound/machines/wewewew.ogg b/sound/machines/wewewew.ogg
new file mode 100644
index 00000000000..b521e0c9761
Binary files /dev/null and b/sound/machines/wewewew.ogg differ
diff --git a/sound/misc/knuckles.ogg b/sound/misc/knuckles.ogg
index 0c8150b0a19..b61a50fb613 100644
Binary files a/sound/misc/knuckles.ogg and b/sound/misc/knuckles.ogg differ
diff --git a/sound/misc/license.txt b/sound/misc/license.txt
index 75a7cc1e93f..b92a4a279a9 100644
--- a/sound/misc/license.txt
+++ b/sound/misc/license.txt
@@ -1,2 +1,5 @@
bloop.ogg by my man Tim Khan
(https://freesound.org/people/tim.kahn/sounds/130377/)
+
+knuckles.ogg by CGEffex. Shortened and cut.
+https://freesound.org/people/CGEffex/sounds/93981/
\ No newline at end of file
diff --git a/sound/roundend/disappointed.ogg b/sound/roundend/disappointed.ogg
index 4a35dc5c513..984bedbcd4b 100644
Binary files a/sound/roundend/disappointed.ogg and b/sound/roundend/disappointed.ogg differ
diff --git a/strings/phobia.json b/strings/phobia.json
index 72eeb39373e..440057928e8 100644
--- a/strings/phobia.json
+++ b/strings/phobia.json
@@ -284,5 +284,18 @@
"neet",
"ora",
"~"
+ ],
+"insects": [
+ "fly",
+ "moth",
+ "buzz",
+ "flutter",
+ "insect",
+ "moff",
+ "roach",
+ "cockroach",
+ "glockroach",
+ "bee",
+ "sting"
]
}
diff --git a/strings/wounds/bone_scar_desc.json b/strings/wounds/bone_scar_desc.json
new file mode 100644
index 00000000000..b1eb84bb8b7
--- /dev/null
+++ b/strings/wounds/bone_scar_desc.json
@@ -0,0 +1,26 @@
+{
+ "generic": ["general disfigurement"],
+
+ "bluntmoderate": [
+ "the bone equivalent of a faded bruise",
+ "a series of tiny chip marks"
+ ],
+
+ "bluntsevere": [
+ "a series of faded hairline cracks",
+ "a small bone dent"
+ ],
+
+ "bluntcritical": [
+ "large streaks of refilled cracks",
+ "a fractal of reformed stress marks",
+ "a cluster of calluses"
+ ],
+
+ "dismember": [
+ "is slightly misaligned",
+ "has clearly been dropped recently",
+ "has a damaged socket"
+ ]
+
+}
diff --git a/strings/wounds/flesh_scar_desc.json b/strings/wounds/flesh_scar_desc.json
new file mode 100644
index 00000000000..047e614b790
--- /dev/null
+++ b/strings/wounds/flesh_scar_desc.json
@@ -0,0 +1,86 @@
+{
+ "generic": ["general disfigurement"],
+
+ "bluntmoderate": [
+ "light discoloring",
+ "a slight blue tint"
+ ],
+
+ "bluntsevere": [
+ "a faded, fist-sized bruise",
+ "a vaguely triangular peel scar"
+ ],
+
+ "bluntcritical": [
+ "a section of janky skin lines and badly healed scars",
+ "a large patch of uneven skin tone",
+ "a cluster of calluses"
+ ],
+
+
+
+ "slashmoderate": [
+ "light, faded lines",
+ "minor cut marks",
+ "a small faded slit",
+ "a series of small scars"
+ ],
+
+ "slashsevere": [
+ "a twisted line of faded gashes",
+ "a gnarled sickle-shaped slice scar"
+ ],
+
+ "slashcritical": [
+ "a winding path of very badly healed scar tissue",
+ "a series of peaks and valleys along a gruesome line of cut scar tissue",
+ "a grotesque snake of indentations and stitching scars"
+ ],
+
+
+
+ "piercemoderate": [
+ "a small, faded bruise",
+ "a small twist of reformed skin",
+ "a thumb-sized puncture scar"
+ ],
+
+ "piercesevere": [
+ "an ink-splat shaped pocket of scar tissue",
+ "a long-faded puncture wound",
+ "a tumbling puncture hole with evidence of faded stitching"
+ ],
+
+ "piercecritical": [
+ "a rippling shockwave of scar tissue",
+ "a wide, scattered cloud of shrapnel marks",
+ "a gruesome multi-pronged puncture scar"
+ ],
+
+
+
+ "burnmoderate": [
+ "small amoeba-shaped skinmarks",
+ "a faded streak of depressed skin"
+ ],
+
+ "burnsevere": [
+ "a large, jagged patch of faded skin",
+ "random spots of shiny, smooth skin",
+ "spots of taut, leathery skin"
+ ],
+
+ "burncritical": [
+ "massive, disfiguring keloid scars",
+ "several long streaks of badly discolored and malformed skin",
+ "unmistakeable splotches of dead tissue from serious burns"
+ ],
+
+
+ "dismember": [
+ "is several skintone shades paler than the rest of the body",
+ "is a gruesome patchwork of artificial flesh",
+ "has a large series of attachment scars at the articulation points"
+ ]
+
+}
diff --git a/strings/wounds/scar_loc.json b/strings/wounds/scar_loc.json
new file mode 100644
index 00000000000..4d1a639a9a1
--- /dev/null
+++ b/strings/wounds/scar_loc.json
@@ -0,0 +1,52 @@
+{
+ "": ["general area"],
+
+ "head": [
+ "left eyebrow",
+ "cheekbone",
+ "neck",
+ "throat",
+ "jawline",
+ "entire face"
+ ],
+
+ "chest": [
+ "upper chest",
+ "lower abdomen",
+ "midsection",
+ "collarbone",
+ "lower back"
+ ],
+
+ "l_arm": [
+ "outer left forearm",
+ "inner left wrist",
+ "left elbow",
+ "left bicep",
+ "left shoulder"
+ ],
+
+ "r_arm": [
+ "outer right forearm",
+ "inner right wrist",
+ "right elbow",
+ "right bicep",
+ "right shoulder"
+ ],
+
+ "l_leg": [
+ "inner left thigh",
+ "outer left calf",
+ "outer left hip",
+ "left kneecap",
+ "lower left shin"
+ ],
+
+ "r_leg": [
+ "inner right thigh",
+ "outer right calf",
+ "outer right hip",
+ "right kneecap",
+ "lower right shin"
+ ]
+}
diff --git a/tgstation.dme b/tgstation.dme
index 919b645023f..eaa7538d93e 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -56,6 +56,7 @@
#include "code\__DEFINES\inventory.dm"
#include "code\__DEFINES\is_helpers.dm"
#include "code\__DEFINES\jobs.dm"
+#include "code\__DEFINES\keybinding.dm"
#include "code\__DEFINES\language.dm"
#include "code\__DEFINES\layers.dm"
#include "code\__DEFINES\lighting.dm"
@@ -287,6 +288,7 @@
#include "code\controllers\subsystem\radiation.dm"
#include "code\controllers\subsystem\radio.dm"
#include "code\controllers\subsystem\research.dm"
+#include "code\controllers\subsystem\runechat.dm"
#include "code\controllers\subsystem\server_maint.dm"
#include "code\controllers\subsystem\shuttle.dm"
#include "code\controllers\subsystem\skills.dm"
@@ -426,6 +428,7 @@
#include "code\datums\components\omen.dm"
#include "code\datums\components\orbiter.dm"
#include "code\datums\components\paintable.dm"
+#include "code\datums\components\payment.dm"
#include "code\datums\components\pellet_cloud.dm"
#include "code\datums\components\pricetag.dm"
#include "code\datums\components\punchcooldown.dm"
@@ -601,6 +604,7 @@
#include "code\datums\mutations\chameleon.dm"
#include "code\datums\mutations\cold.dm"
#include "code\datums\mutations\hulk.dm"
+#include "code\datums\mutations\passive.dm"
#include "code\datums\mutations\radioactive.dm"
#include "code\datums\mutations\sight.dm"
#include "code\datums\mutations\space_adaptation.dm"
@@ -652,7 +656,9 @@
#include "code\datums\wounds\_wounds.dm"
#include "code\datums\wounds\bones.dm"
#include "code\datums\wounds\burns.dm"
-#include "code\datums\wounds\cuts.dm"
+#include "code\datums\wounds\loss.dm"
+#include "code\datums\wounds\pierce.dm"
+#include "code\datums\wounds\slash.dm"
#include "code\datums\wounds\scars\_scars.dm"
#include "code\game\alternate_appearance.dm"
#include "code\game\atoms.dm"
@@ -724,6 +730,7 @@
#include "code\game\machinery\buttons.dm"
#include "code\game\machinery\canister_frame.dm"
#include "code\game\machinery\cell_charger.dm"
+#include "code\game\machinery\civilian_bountys.dm"
#include "code\game\machinery\constructable_frame.dm"
#include "code\game\machinery\dance_machine.dm"
#include "code\game\machinery\defibrillator_mount.dm"
@@ -1125,6 +1132,7 @@
#include "code\game\objects\items\stacks\tiles\tile_types.dm"
#include "code\game\objects\items\storage\backpack.dm"
#include "code\game\objects\items\storage\bags.dm"
+#include "code\game\objects\items\storage\basket.dm"
#include "code\game\objects\items\storage\belt.dm"
#include "code\game\objects\items\storage\book.dm"
#include "code\game\objects\items\storage\boxes.dm"
@@ -1526,7 +1534,6 @@
#include "code\modules\antagonists\space_dragon\space_dragon.dm"
#include "code\modules\antagonists\survivalist\survivalist.dm"
#include "code\modules\antagonists\swarmer\swarmer.dm"
-#include "code\modules\antagonists\swarmer\swarmer_event.dm"
#include "code\modules\antagonists\traitor\datum_traitor.dm"
#include "code\modules\antagonists\traitor\syndicate_contract.dm"
#include "code\modules\antagonists\traitor\equipment\contractor.dm"
@@ -1642,8 +1649,10 @@
#include "code\modules\buildmode\submodes\basic.dm"
#include "code\modules\buildmode\submodes\boom.dm"
#include "code\modules\buildmode\submodes\copy.dm"
+#include "code\modules\buildmode\submodes\delete.dm"
#include "code\modules\buildmode\submodes\fill.dm"
#include "code\modules\buildmode\submodes\mapgen.dm"
+#include "code\modules\buildmode\submodes\outfit.dm"
#include "code\modules\buildmode\submodes\throwing.dm"
#include "code\modules\buildmode\submodes\variable_edit.dm"
#include "code\modules\cargo\bounty.dm"
@@ -1837,6 +1846,7 @@
#include "code\modules\events\immovable_rod.dm"
#include "code\modules\events\ion_storm.dm"
#include "code\modules\events\major_dust.dm"
+#include "code\modules\events\market_crash.dm"
#include "code\modules\events\mass_hallucination.dm"
#include "code\modules\events\meateor_wave.dm"
#include "code\modules\events\meteor_wave.dm"
@@ -1855,6 +1865,7 @@
#include "code\modules\events\spider_infestation.dm"
#include "code\modules\events\spontaneous_appendicitis.dm"
#include "code\modules\events\stray_cargo.dm"
+#include "code\modules\events\swarmer.dm"
#include "code\modules\events\vent_clog.dm"
#include "code\modules\events\wisdomcow.dm"
#include "code\modules\events\wormholes.dm"
@@ -2074,6 +2085,8 @@
#include "code\modules\library\lib_machines.dm"
#include "code\modules\library\random_books.dm"
#include "code\modules\library\soapstone.dm"
+#include "code\modules\library\skill_learning\skill_station.dm"
+#include "code\modules\library\skill_learning\skillchip.dm"
#include "code\modules\lighting\emissive_blocker.dm"
#include "code\modules\lighting\lighting_area.dm"
#include "code\modules\lighting\lighting_atom.dm"
@@ -2111,7 +2124,6 @@
#include "code\modules\mining\machine_vending.dm"
#include "code\modules\mining\mine_items.dm"
#include "code\modules\mining\minebot.dm"
-#include "code\modules\mining\mint.dm"
#include "code\modules\mining\money_bag.dm"
#include "code\modules\mining\ores_coins.dm"
#include "code\modules\mining\satchel_ore_boxdm.dm"
@@ -3015,6 +3027,7 @@
#include "code\modules\surgery\plastic_surgery.dm"
#include "code\modules\surgery\prosthetic_replacement.dm"
#include "code\modules\surgery\remove_embedded_object.dm"
+#include "code\modules\surgery\repair_puncture.dm"
#include "code\modules\surgery\revival.dm"
#include "code\modules\surgery\stomachpump.dm"
#include "code\modules\surgery\surgery.dm"
@@ -3060,6 +3073,9 @@
#include "code\modules\surgery\organs\tails.dm"
#include "code\modules\surgery\organs\tongue.dm"
#include "code\modules\surgery\organs\vocal_cords.dm"
+#include "code\modules\swarmers\swarmer.dm"
+#include "code\modules\swarmers\swarmer_act.dm"
+#include "code\modules\swarmers\swarmer_objs.dm"
#include "code\modules\tgs\includes.dm"
#include "code\modules\tgui\external.dm"
#include "code\modules\tgui\states.dm"
diff --git a/tgui/package.json b/tgui/package.json
index 8dd15925b2a..d0ef805c470 100644
--- a/tgui/package.json
+++ b/tgui/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "tgui",
- "version": "3.0.0",
+ "version": "4.1.0",
"workspaces": [
"packages/*"
],
diff --git a/tgui/packages/common/package.json b/tgui/packages/common/package.json
index fbd255ababc..22560d92f93 100644
--- a/tgui/packages/common/package.json
+++ b/tgui/packages/common/package.json
@@ -1,6 +1,6 @@
{
"private": true,
"name": "common",
- "version": "3.0.0",
+ "version": "4.1.0",
"type": "module"
}
diff --git a/tgui/packages/common/storage.js b/tgui/packages/common/storage.js
index bcb010e77a7..8e1d2183e42 100644
--- a/tgui/packages/common/storage.js
+++ b/tgui/packages/common/storage.js
@@ -58,7 +58,19 @@ const createLocalStorage = () => {
};
};
+const testLocalStorage = () => {
+ // Localstorage can sometimes throw an error, even if DOM storage is not
+ // disabled in IE11 settings.
+ // See: https://superuser.com/questions/1080011
+ try {
+ return Boolean(window.localStorage && window.localStorage.getItem);
+ }
+ catch {
+ return false;
+ }
+};
+
export const storage = (
- window.localStorage && createLocalStorage()
+ testLocalStorage() && createLocalStorage()
|| createMock()
);
diff --git a/tgui/packages/tgui-dev-server/link/client.js b/tgui/packages/tgui-dev-server/link/client.js
index af7a920c64c..4671b340c53 100644
--- a/tgui/packages/tgui-dev-server/link/client.js
+++ b/tgui/packages/tgui-dev-server/link/client.js
@@ -98,8 +98,8 @@ const sendRawMessage = msg => {
socket.send(json);
}
else {
- // Keep only 10 latest messages in the queue
- if (queue.length > 10) {
+ // Keep only 100 latest messages in the queue
+ if (queue.length > 100) {
queue.shift();
}
queue.push(json);
diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json
index e397fe3e0bb..bcb02196e65 100644
--- a/tgui/packages/tgui-dev-server/package.json
+++ b/tgui/packages/tgui-dev-server/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "tgui-dev-server",
- "version": "3.0.0",
+ "version": "4.1.0",
"type": "module",
"dependencies": {
"glob": "^7.1.4",
diff --git a/tgui/packages/tgui/assets.js b/tgui/packages/tgui/assets.js
index 2023a5c8842..b0f71bb9ffa 100644
--- a/tgui/packages/tgui/assets.js
+++ b/tgui/packages/tgui/assets.js
@@ -9,15 +9,46 @@ import { createLogger } from './logging';
const logger = createLogger('assets');
-const loadedAssets = {
- styles: [],
-};
+const EXCLUDED_PATTERNS = [
+ /v4shim/i,
+];
-export const loadCSS = filename => {
- if (loadedAssets.styles.includes(filename)) {
+const loadedStyles = [];
+const loadedMappings = {};
+
+export const loadCSS = url => {
+ if (loadedStyles.includes(url)) {
return;
}
- loadedAssets.styles.push(filename);
- logger.log(`loading stylesheet '${filename}'`);
- fgLoadCSS(filename);
+ loadedStyles.push(url);
+ logger.log(`loading stylesheet '${url}'`);
+ fgLoadCSS(url);
+};
+
+export const resolveAsset = name => (
+ loadedMappings[name] || name
+);
+
+export const assetMiddleware = store => next => action => {
+ const { type, payload } = action;
+ if (type === 'asset/stylesheet') {
+ loadCSS(payload);
+ return;
+ }
+ if (type === 'asset/mappings') {
+ for (let name of Object.keys(payload)) {
+ // Skip anything that matches excluded patterns
+ if (EXCLUDED_PATTERNS.some(regex => regex.test(name))) {
+ continue;
+ }
+ const url = payload[name];
+ const ext = name.split('.').pop();
+ loadedMappings[name] = url;
+ if (ext === 'css') {
+ loadCSS(url);
+ }
+ }
+ return;
+ }
+ next(action);
};
diff --git a/tgui/packages/tgui/backend.js b/tgui/packages/tgui/backend.js
index 2c9bd093721..dab86ee918c 100644
--- a/tgui/packages/tgui/backend.js
+++ b/tgui/packages/tgui/backend.js
@@ -79,7 +79,6 @@ export const backendReducer = (state = initialState, action) => {
return {
...state,
config,
- assets: payload.assets || {},
data,
shared,
visible,
@@ -253,7 +252,6 @@ export const sendAct = (action, payload = {}) => {
* },
* },
* data: any,
- * assets: any,
* shared: any,
* visible: boolean,
* interactive: boolean,
diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js
index 0b2f1a652eb..f554c1c319f 100644
--- a/tgui/packages/tgui/index.js
+++ b/tgui/packages/tgui/index.js
@@ -30,7 +30,6 @@ import './styles/themes/syndicate.scss';
import { perf } from 'common/perf';
import { render } from 'inferno';
import { setupHotReloading } from 'tgui-dev-server/link/client';
-import { loadCSS } from './assets';
import { backendUpdate, backendSuspendSuccess, selectBackend, sendMessage } from './backend';
import { setupDrag } from './drag';
import { logger } from './logging';
@@ -96,8 +95,6 @@ const renderLayout = () => {
if (initialRender) {
initialRender = false;
}
- // Load assets
- assets?.styles?.forEach(filename => loadCSS(filename));
};
// Parse JSON and report all abnormal JSON strings coming from BYOND
@@ -143,9 +140,8 @@ const setupApp = () => {
logger.debug(`received message '${message?.type}'`);
const { type, payload } = message;
if (type === 'update') {
- window.__ref__ = payload.config.ref;
if (suspended) {
- logger.log('reinitializing to:', payload.config.ref);
+ logger.log('resuming');
initialRender = 'recycled';
}
// Backend update dispatches a store action
@@ -162,7 +158,8 @@ const setupApp = () => {
});
return;
}
- logger.log('unhandled message', message);
+ // Pass the message directly to the store
+ store.dispatch(message);
};
// Enable hot module reloading
@@ -185,9 +182,6 @@ const setupApp = () => {
}
window.update(stateJson);
}
-
- // Dynamically load font-awesome from browser's cache
- loadCSS('font-awesome.css');
};
// Setup a fatal error reporter
diff --git a/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.js b/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.js
new file mode 100644
index 00000000000..5fe38744228
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.js
@@ -0,0 +1,103 @@
+import { Fragment } from 'inferno';
+import { useBackend } from '../backend';
+import { AnimatedNumber, Box, Button, Flex, LabeledList, NoticeBox, Section } from '../components';
+import { Window } from '../layouts';
+
+export const CivCargoHoldTerminal = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ pad,
+ sending,
+ status_report,
+ id_inserted,
+ id_bounty_info,
+ id_bounty_value,
+ id_bounty_num,
+ } = data;
+ const in_text = "Welcome valued employee.";
+ const out_text = "To begin, insert your ID into the console.";
+ return (
+
+
+
+
+
+ {id_inserted ? in_text : out_text}
+
+
+
+
+ {pad ? "Online" : "Not Found"}
+
+
+ {status_report}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const BountyTextBox = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ id_bounty_info,
+ id_bounty_value,
+ id_bounty_num,
+ } = data;
+ const na_text = "N/A, please add a new bounty.";
+ return (
+
+
+
+ {id_bounty_info ? id_bounty_info : na_text}
+
+
+ {id_bounty_info ? id_bounty_num : "N/A"}
+
+
+ {id_bounty_info ? id_bounty_value : "N/A"}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/DnaConsole.js b/tgui/packages/tgui/interfaces/DnaConsole.js
index 2cb9293e125..194b9a4a6b5 100644
--- a/tgui/packages/tgui/interfaces/DnaConsole.js
+++ b/tgui/packages/tgui/interfaces/DnaConsole.js
@@ -3,6 +3,7 @@ import { flow } from 'common/fp';
import { classes } from 'common/react';
import { capitalize } from 'common/string';
import { Fragment } from 'inferno';
+import { resolveAsset } from '../assets';
import { useBackend } from '../backend';
import { Box, Button, Collapsible, Dimmer, Divider, Dropdown, Flex, Icon, LabeledList, NumberInput, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
@@ -777,7 +778,7 @@ const DnaConsoleSequencer = (props, context) => {
{mutations.map(mutation => (
{
act('set_view', {
diff --git a/tgui/packages/tgui/interfaces/MafiaPanel.js b/tgui/packages/tgui/interfaces/MafiaPanel.js
index e3c2c893ecc..e12d4b060b4 100644
--- a/tgui/packages/tgui/interfaces/MafiaPanel.js
+++ b/tgui/packages/tgui/interfaces/MafiaPanel.js
@@ -24,6 +24,51 @@ export const MafiaPanel = (props, context) => {
height={550}
resizable>
+ {!!admin_controls && (
+
+ THESE ARE DEBUG, THEY WILL BREAK THE GAME, DO NOT TOUCH
+ Also because an admin did it: do not gib/delete/etc
+ anyone! It will runtime the game to death!
+ act("next_phase")}>
+ Next Phase
+
+ act("players_home")}>
+ Send All Players Home
+
+ act("new_game")}>
+ New Game
+
+
+ This makes the next game what you input.
+ Resets after one round automatically.
+
+ act("debug_setup")}>
+ Create Custom Setup
+
+ act("cancel_setup")}>
+ Reset Custom Setup
+
+
+ act("nuke")}
+ color="black">
+ Nuke (delete datum + landmarks, hope it fixes everything!)
+
+
+ )}
{!!roleinfo && (
@@ -49,37 +94,6 @@ export const MafiaPanel = (props, context) => {
))}
- {!!admin_controls && (
-
- THESE ARE DEBUG, THEY WILL BREAK THE GAME, DO NOT TOUCH
- Also because an admin did it: do not gib/delete/etc
- anyone! It will runtime the game to death!
- act("next_phase")}>
- Next Phase
-
- act("players_home")}>
- Send All Players Home
-
- act("new_game")}>
- New Game
-
-
- act("nuke")}
- color="black">
- Nuke (delete datum + landmarks, hope it fixes everything!)
-
-
- )}
{!!players && players.map(player => (
diff --git a/tgui/packages/tgui/interfaces/Mint.js b/tgui/packages/tgui/interfaces/Mint.js
deleted file mode 100644
index 1b7e1456c3e..00000000000
--- a/tgui/packages/tgui/interfaces/Mint.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useBackend } from '../backend';
-import { Button, LabeledList, Section } from '../components';
-import { Window } from '../layouts';
-
-export const Mint = (props, context) => {
- const { act, data } = useBackend(context);
- const inserted_materials = data.inserted_materials || [];
- return (
-
-
- act(data.processing
- ? 'stoppress'
- : 'startpress')} />
- }>
-
- {inserted_materials.map(material => (
- act('changematerial', {
- material_name: material.material,
- })} />
- )}>
- {material.amount} cm³
-
- ))}
-
-
-
- Pressed {data.produced_coins} coins this cycle.
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/NtosArcade.js b/tgui/packages/tgui/interfaces/NtosArcade.js
index 67c85db7be4..f0f7241ab87 100644
--- a/tgui/packages/tgui/interfaces/NtosArcade.js
+++ b/tgui/packages/tgui/interfaces/NtosArcade.js
@@ -1,3 +1,4 @@
+import { resolveAsset } from '../assets';
import { useBackend } from '../backend';
import { AnimatedNumber, Box, Button, Grid, LabeledList, ProgressBar, Section } from '../components';
import { NtosWindow } from '../layouts';
@@ -73,7 +74,7 @@ export const NtosArcade = (props, context) => {
inline
width="156px"
textAlign="center">
-
+
diff --git a/tgui/packages/tgui/interfaces/NtosRadar.js b/tgui/packages/tgui/interfaces/NtosRadar.js
index 46b20546fa0..2d2fe6a452f 100644
--- a/tgui/packages/tgui/interfaces/NtosRadar.js
+++ b/tgui/packages/tgui/interfaces/NtosRadar.js
@@ -1,4 +1,5 @@
import { classes } from 'common/react';
+import { resolveAsset } from '../assets';
import { useBackend } from '../backend';
import { Box, Button, Flex, Icon, NoticeBox, Section } from '../components';
import { NtosWindow } from '../layouts';
@@ -68,7 +69,9 @@ export const NtosRadarContent = (props, context) => {
{
)
: !!target.userot && (
+ }} />
) || (
{
size={2}
color={target.color}
top={((target.locy * 10) + 19) + 'px'}
- left={((target.locx * 10) + 16) + 'px'}
- />
+ left={((target.locx * 10) + 16) + 'px'} />
)}
diff --git a/tgui/packages/tgui/interfaces/Orbit.js b/tgui/packages/tgui/interfaces/Orbit.js
index 4bc2d4d5ca0..7423dadbc4c 100644
--- a/tgui/packages/tgui/interfaces/Orbit.js
+++ b/tgui/packages/tgui/interfaces/Orbit.js
@@ -1,4 +1,5 @@
import { createSearch } from 'common/string';
+import { resolveAsset } from '../assets';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Flex, Icon, Input, Section } from '../components';
import { Window } from '../layouts';
@@ -67,7 +68,7 @@ const OrbitedButton = (props, context) => {
{"("}{thing.orbiters}{" "}
{")"}
diff --git a/tgui/packages/tgui/interfaces/SkillStation.js b/tgui/packages/tgui/interfaces/SkillStation.js
new file mode 100644
index 00000000000..8166cdaebe2
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/SkillStation.js
@@ -0,0 +1,110 @@
+import { useBackend } from '../backend';
+import { Button, Box, Section, NoticeBox, TimeDisplay, Flex, Icon, Table } from '../components';
+import { Window } from '../layouts';
+import { Fragment } from 'inferno';
+import { FlexItem } from '../components/Flex';
+
+export const SkillStation = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ working,
+ timeleft,
+ error,
+ current = [],
+ slots_used,
+ slots_max,
+ skillchip_ready,
+ implantable,
+ implantable_reason,
+ skill_name,
+ skill_desc,
+ skill_icon,
+ skill_cost,
+ } = data;
+ let skillchip_section_content;
+ if (!skillchip_ready) {
+ // I guess could use something better
+ skillchip_section_content = "Insert a skillchip to continue.";
+ } else {
+ skillchip_section_content = (
+
+
+
+
+
+ {skill_name}
+ {skill_desc}
+ Complexity: {skill_cost}
+ {!!implantable_reason && (
+
+ {implantable_reason}
+ )}
+
+
+ act("implant")}>Implant
+
+ act("eject")}>Eject
+
+ );
+ }
+ return (
+
+
+ {!!error && ({error})}
+ {!!working && (
+
+ Operation in progress. Please do not leave the chamber.
+ Time Left :
+ )}
+ {!working && ({skillchip_section_content})}
+ {slots_used}/{slots_max}}>
+ {!current.length && "No skillchips detected."}
+ {!!current.length && (
+