TG performance tweaks - oh-god-please-kill-me-edition ASYNC / SLEEP BAD / Profiler (#10207)

* Im sleepy

* Reee

* oopsie Linter go brrrr

* Update code/datums/components/storage/concrete/bag_of_holding.dm

Co-authored-by: Nichlas Pihl <nichlas00100@gmail.com>

* Update code/controllers/subsystem/air.dm

Co-authored-by: Nichlas Pihl <nichlas00100@gmail.com>
This commit is contained in:
Jamie D
2020-10-29 19:28:57 +00:00
committed by GitHub
parent f666695656
commit 38f3174826
48 changed files with 311 additions and 202 deletions

View File

@@ -9,6 +9,8 @@
#define CLEAN_IMPRESSIVE 5
/// Cleans things spotless down to the atomic structure
#define CLEAN_GOD 6
/// Never cleaned
#define CLEAN_NEVER 7
//How strong things have to be to wipe forensic evidence...
#define CLEAN_STRENGTH_FINGERPRINTS CLEAN_IMPRESSIVE

View File

@@ -478,3 +478,5 @@ GLOBAL_LIST_INIT(pda_styles, list(MONO, VT, ORBITRON, SHARE))
#define ALIGNMENT_NEUT "neutral"
#define ALIGNMENT_EVIL "evil"
// \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
#define REF(thing) (istype(thing, /datum) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : "\ref[thing]")

View File

@@ -98,6 +98,7 @@
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
#define INIT_ORDER_PROFILER 101
#define INIT_ORDER_TITLE 100
#define INIT_ORDER_GARBAGE 99
#define INIT_ORDER_STATPANELS 98

View File

@@ -174,11 +174,11 @@
/proc/recursive_hear_check(O)
var/list/processing_list = list(O)
. = list()
while(processing_list.len)
var/atom/A = processing_list[1]
var/i = 0
while(i < length(processing_list))
var/atom/A = processing_list[++i]
if(A.flags_1 & HEAR_1)
. += A
processing_list.Cut(1, 2)
processing_list += A.contents
/** recursive_organ_check
@@ -264,10 +264,8 @@
// Returns a list of hearers in view(R) from source (ignoring luminosity). Used in saycode.
var/turf/T = get_turf(source)
. = list()
if(!T)
return
var/list/processing_list = list()
if (R == 0) // if the range is zero, we know exactly where to look for, we can skip view
processing_list += T.contents // We can shave off one iteration by assuming turfs cannot hear
@@ -280,11 +278,11 @@
processing_list += O
T.luminosity = lum
while(processing_list.len) // recursive_hear_check inlined here
var/atom/A = processing_list[1]
var/i = 0
while(i < length(processing_list)) // recursive_hear_check inlined here
var/atom/A = processing_list[++i]
if(A.flags_1 & HEAR_1)
. += A
processing_list.Cut(1, 2)
processing_list += A.contents
/proc/get_mobs_in_radio_ranges(list/obj/item/radio/radios)

View File

@@ -449,38 +449,38 @@ Turf and target are separate in case you want to teleport some distance from a t
Gets all contents of contents and returns them all in a list.
*/
/atom/proc/GetAllContents(var/T)
/atom/proc/GetAllContents(T, ignore_flag_1)
var/list/processing_list = list(src)
var/list/assembled = list()
if(T)
while(processing_list.len)
var/atom/A = processing_list[1]
processing_list.Cut(1, 2)
. = list()
var/i = 0
while(i < length(processing_list))
var/atom/A = processing_list[++i]
//Byond does not allow things to be in multiple contents, or double parent-child hierarchies, so only += is needed
//This is also why we don't need to check against assembled as we go along
processing_list += A.contents
if(istype(A,T))
assembled += A
if (!(A.flags_1 & ignore_flag_1))
processing_list += A.contents
if(istype(A,T))
. += A
else
while(processing_list.len)
var/atom/A = processing_list[1]
processing_list.Cut(1, 2)
processing_list += A.contents
assembled += A
return assembled
var/i = 0
while(i < length(processing_list))
var/atom/A = processing_list[++i]
if (!(A.flags_1 & ignore_flag_1))
processing_list += A.contents
return processing_list
/atom/proc/GetAllContentsIgnoring(list/ignore_typecache)
if(!length(ignore_typecache))
return GetAllContents()
var/list/processing = list(src)
var/list/assembled = list()
while(processing.len)
var/atom/A = processing[1]
processing.Cut(1,2)
. = list()
var/i = 0
while(i < length(processing))
var/atom/A = processing[++i]
if(!ignore_typecache[A.type])
processing += A.contents
assembled += A
return assembled
. += A
//Step-towards method of determining whether one atom can see another. Similar to viewers()
/proc/can_see(atom/source, atom/target, length=5) // I couldnt be arsed to do actual raycasting :I This is horribly inaccurate.
@@ -1396,20 +1396,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
return "{[time_high]-[time_mid]-[GUID_VERSION][time_low]-[GUID_VARIANT][time_clock]-[node_id]}"
// \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
// If it ever becomes necesary to get a more performant REF(), this lies here in wait
// #define REF(thing) (thing && istype(thing, /datum) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : "\ref[thing]")
/proc/REF(input)
if(istype(input, /datum))
var/datum/thing = input
if(thing.datum_flags & DF_USE_TAG)
if(!thing.tag)
stack_trace("A ref was requested of an object with DF_USE_TAG set but no tag: [thing]")
thing.datum_flags &= ~DF_USE_TAG
else
return "\[[url_encode(thing.tag)]\]"
return "\ref[input]"
// Makes a call in the context of a different usr
// Use sparingly
/world/proc/PushUsr(mob/M, datum/callback/CB, ...)

View File

@@ -495,3 +495,5 @@
/datum/config_entry/flag/everyone_is_donator
/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
/datum/config_entry/flag/auto_profile

View File

@@ -45,9 +45,9 @@
return
//This is used so the mc knows when the subsystem sleeps. do not override.
/datum/controller/subsystem/proc/ignite(resumed = 0)
/datum/controller/subsystem/proc/ignite(resumed = FALSE)
SHOULD_NOT_OVERRIDE(TRUE)
set waitfor = 0
set waitfor = FALSE
. = SS_SLEEPING
fire(resumed)
. = state
@@ -62,7 +62,7 @@
//previously, this would have been named 'process()' but that name is used everywhere for different things!
//fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds.
//Sleeping in here prevents future fires until returned.
/datum/controller/subsystem/proc/fire(resumed = 0)
/datum/controller/subsystem/proc/fire(resumed = FALSE)
flags |= SS_NO_FIRE
CRASH("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.")

View File

@@ -6,6 +6,7 @@ SUBSYSTEM_DEF(air)
flags = SS_BACKGROUND
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/cached_cost = 0
var/cost_turfs = 0
var/cost_groups = 0
var/cost_highpressure = 0
@@ -85,74 +86,93 @@ SUBSYSTEM_DEF(air)
resumed = FALSE
currentpart = SSAIR_PIPENETS
if(currentpart == SSAIR_PIPENETS || !resumed)
timer = TICK_USAGE_REAL
if(!resumed)
cached_cost = 0
process_pipenets(resumed)
cost_pipenets = MC_AVERAGE(cost_pipenets, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
cached_cost += TICK_USAGE_REAL - timer
if(state != SS_RUNNING)
return
resumed = 0
cost_pipenets = MC_AVERAGE(cost_pipenets, TICK_DELTA_TO_MS(cached_cost))
resumed = FALSE
currentpart = SSAIR_ATMOSMACHINERY
if(currentpart == SSAIR_ATMOSMACHINERY)
timer = TICK_USAGE_REAL
if(!resumed)
cached_cost = 0
process_atmos_machinery(resumed)
cost_atmos_machinery = MC_AVERAGE(cost_atmos_machinery, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
cached_cost += TICK_USAGE_REAL - timer
if(state != SS_RUNNING)
return
resumed = 0
cost_atmos_machinery = MC_AVERAGE(cost_atmos_machinery, TICK_DELTA_TO_MS(cached_cost))
resumed = FALSE
currentpart = SSAIR_EQUALIZE
if(currentpart == SSAIR_EQUALIZE)
timer = TICK_USAGE_REAL
if(!resumed)
cached_cost = 0
process_turf_equalize(resumed)
cost_equalize = MC_AVERAGE(cost_equalize, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
cost_equalize = MC_AVERAGE(cost_equalize, TICK_DELTA_TO_MS(cached_cost))
resumed = FALSE
currentpart = SSAIR_ACTIVETURFS
if(currentpart == SSAIR_ACTIVETURFS)
timer = TICK_USAGE_REAL
if(!resumed)
cached_cost = 0
process_active_turfs(resumed)
cost_turfs = MC_AVERAGE(cost_turfs, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
cost_turfs = MC_AVERAGE(cost_turfs, TICK_DELTA_TO_MS(cached_cost))
resumed = FALSE
currentpart = SSAIR_EXCITEDGROUPS
if(currentpart == SSAIR_EXCITEDGROUPS)
timer = TICK_USAGE_REAL
if(!resumed)
cached_cost = 0
process_excited_groups(resumed)
cost_groups = MC_AVERAGE(cost_groups, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
cost_groups = MC_AVERAGE(cost_groups, TICK_DELTA_TO_MS(cached_cost))
resumed = FALSE
currentpart = SSAIR_HIGHPRESSURE
if(currentpart == SSAIR_HIGHPRESSURE)
timer = TICK_USAGE_REAL
if(!resumed)
cached_cost = 0
process_high_pressure_delta(resumed)
cost_highpressure = MC_AVERAGE(cost_highpressure, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
cost_highpressure = MC_AVERAGE(cost_highpressure, TICK_DELTA_TO_MS(cached_cost))
resumed = FALSE
currentpart = SSAIR_HOTSPOTS
if(currentpart == SSAIR_HOTSPOTS)
timer = TICK_USAGE_REAL
if(!resumed)
cached_cost = 0
process_hotspots(resumed)
cost_hotspots = MC_AVERAGE(cost_hotspots, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
cost_hotspots = MC_AVERAGE(cost_hotspots, TICK_DELTA_TO_MS(cached_cost))
resumed = FALSE
currentpart = SSAIR_SUPERCONDUCTIVITY
if(currentpart == SSAIR_SUPERCONDUCTIVITY)
timer = TICK_USAGE_REAL
if(!resumed)
cached_cost = 0
process_super_conductivity(resumed)
cost_superconductivity = MC_AVERAGE(cost_superconductivity, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
cost_superconductivity = MC_AVERAGE(cost_superconductivity, TICK_DELTA_TO_MS(cached_cost))
resumed = FALSE
currentpart = SSAIR_REBUILD_PIPENETS

View File

@@ -0,0 +1,64 @@
#define PROFILER_FILENAME "profiler.json"
SUBSYSTEM_DEF(profiler)
name = "Profiler"
init_order = INIT_ORDER_PROFILER
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
wait = 3000
flags = SS_NO_TICK_CHECK
var/fetch_cost = 0
var/write_cost = 0
/datum/controller/subsystem/profiler/stat_entry(msg)
msg += "F:[round(fetch_cost,1)]ms"
msg += "|W:[round(write_cost,1)]ms"
..(msg)
/datum/controller/subsystem/profiler/Initialize()
if(CONFIG_GET(flag/auto_profile))
StartProfiling()
else
StopProfiling() //Stop the early start from world/New
return ..()
/datum/controller/subsystem/profiler/fire()
if(CONFIG_GET(flag/auto_profile))
DumpFile()
/datum/controller/subsystem/profiler/Shutdown()
if(CONFIG_GET(flag/auto_profile))
DumpFile()
return ..()
/datum/controller/subsystem/profiler/proc/StartProfiling()
#if DM_BUILD < 1506 || DM_VERSION < 513
stack_trace("Auto profiling unsupported on this byond version")
CONFIG_SET(flag/auto_profile, FALSE)
#else
world.Profile(PROFILE_START)
#endif
/datum/controller/subsystem/profiler/proc/StopProfiling()
#if DM_BUILD >= 1506 && DM_VERSION >= 513
world.Profile(PROFILE_STOP)
#endif
/datum/controller/subsystem/profiler/proc/DumpFile()
#if DM_BUILD < 1506 || DM_VERSION < 513
stack_trace("Auto profiling unsupported on this byond version")
CONFIG_SET(flag/auto_profile, FALSE)
#else
var/timer = TICK_USAGE_REAL
var/current_profile_data = world.Profile(PROFILE_REFRESH,format="json")
fetch_cost = MC_AVERAGE(fetch_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
CHECK_TICK
if(!length(current_profile_data)) //Would be nice to have explicit proc to check this
stack_trace("Warning, profiling stopped manually before dump.")
var/json_file = file("[GLOB.log_directory]/[PROFILER_FILENAME]")
if(fexists(json_file))
fdel(json_file)
timer = TICK_USAGE_REAL
WRITE_FILE(json_file, current_profile_data)
write_cost = MC_AVERAGE(write_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
WRITE_FILE(json_file, current_profile_data)
#endif

View File

@@ -87,7 +87,7 @@
owner.vomit()
fail = TRUE
if(2)
owner.emote("cough")
INVOKE_ASYNC(owner, /mob.proc/emote, "cough")
owner.dizziness += 10
fail = TRUE
if(3)

View File

@@ -7,7 +7,7 @@
var/first_dir // This only stores the dir arg from init
/datum/component/decal/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_GOD, _color, _layer=TURF_LAYER, _description, _alpha=255)
/datum/component/decal/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_NEVER, _color, _layer=TURF_LAYER, _description, _alpha=255)
if(!isatom(parent) || !generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha))
return COMPONENT_INCOMPATIBLE
first_dir = _dir
@@ -19,7 +19,7 @@
/datum/component/decal/RegisterWithParent()
if(first_dir)
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_react)
if(cleanable)
if(cleanable != CLEAN_NEVER)
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_react)
if(description)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)

View File

@@ -5,43 +5,47 @@
var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.GetAllContents(), typecacheof(/obj/item/storage/backpack/holding))
matching -= A
if(istype(W, /obj/item/storage/backpack/holding) || matching.len)
var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Abort", "Proceed")
if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user)))
return
var/turf/loccheck = get_turf(A)
if(is_reebe(loccheck.z))
user.visible_message("<span class='warning'>An unseen force knocks [user] to the ground!</span>", "<span class='big_brass'>\"I think not!\"</span>")
user.Paralyze(60)
return
if(istype(loccheck.loc, /area/fabric_of_reality))
to_chat(user, "<span class='danger'>You can't do that here!</span>")
to_chat(user, "<span class='danger'>The Bluespace interfaces of the two devices catastrophically malfunction!</span>")
qdel(W)
playsound(loccheck,'sound/effects/supermatter.ogg', 200, 1)
message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].")
log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].")
for(var/turf/T in range(2,loccheck))
if(istype(T, /turf/open/space/transit))
continue
for(var/atom/AT in T)
AT.emp_act(EMP_HEAVY)
if(istype(AT, /obj))
var/obj/O = AT
O.obj_break()
if(istype(AT, /mob/living))
var/mob/living/M = AT
M.take_overall_damage(85)
if(M.movement_type & FLYING)
M.visible_message("<span class='danger'>The bluespace collapse crushes the air towards it, pulling [M] towards the ground...</span>")
M.Paralyze(5, TRUE, TRUE) //Overrides stun absorbs.
T.TerraformTurf(/turf/open/chasm/magic, /turf/open/chasm/magic)
for(var/fabricarea in get_areas(/area/fabric_of_reality))
var/area/fabric_of_reality/R = fabricarea
R.origin = loccheck
for (var/obj/structure/ladder/unbreakable/binary/ladder in GLOB.ladders)
ladder.ActivateAlmonds()
qdel(A)
INVOKE_ASYNC(src, .proc/recursive_insertion, W, user)
return
. = ..()
/datum/component/storage/concrete/bluespace/bag_of_holding/proc/recursive_insertion(obj/item/W, mob/living/user)
var/atom/A = parent
var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Abort", "Proceed")
if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user)))
return
var/turf/loccheck = get_turf_global(A)
if(is_reebe(loccheck.z))
user.visible_message("<span class='warning'>An unseen force knocks [user] to the ground!</span>", "<span class='big_brass'>\"I think not!\"</span>")
user.Paralyze(60)
return
if(istype(loccheck.loc, /area/fabric_of_reality))
to_chat(user, "<span class='danger'>You can't do that here!</span>")
to_chat(user, "<span class='danger'>The Bluespace interfaces of the two devices catastrophically malfunction!</span>")
qdel(W)
playsound(loccheck,'sound/effects/supermatter.ogg', 200, 1)
message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].")
log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].")
for(var/turf/T in range(2,loccheck))
if(istype(T, /turf/open/space/transit))
continue
for(var/atom/AT in T)
AT.emp_act(EMP_HEAVY)
if(istype(AT, /obj))
var/obj/O = AT
O.obj_break()
if(istype(AT, /mob/living))
var/mob/living/M = AT
M.take_overall_damage(85)
if(M.movement_type & FLYING)
M.visible_message("<span class='danger'>The bluespace collapse crushes the air towards it, pulling [M] towards the ground...</span>")
M.Paralyze(5, TRUE, TRUE) //Overrides stun absorbs.
T.TerraformTurf(/turf/open/chasm/magic, /turf/open/chasm/magic)
for(var/fabricarea in get_areas(/area/fabric_of_reality))
var/area/fabric_of_reality/R = fabricarea
R.origin = loccheck
for (var/obj/structure/ladder/unbreakable/binary/ladder in GLOB.ladders)
ladder.ActivateAlmonds()
qdel(A)

View File

@@ -78,6 +78,8 @@
SSticker.minds -= src
if(islist(antag_datums))
QDEL_LIST(antag_datums)
current = null
soulOwner = null
return ..()
/datum/mind/proc/get_language_holder()

View File

@@ -17,4 +17,4 @@
return visual_indicators[type][1]
/datum/mutation/human/telekinesis/on_ranged_attack(atom/target)
target.attack_tk(owner)
INVOKE_ASYNC(target, /atom.proc/attack_tk, owner)

View File

@@ -13,6 +13,7 @@
/datum/status_effect/crusher_damage //tracks the damage dealt to this mob by kinetic crushers
id = "crusher_damage"
duration = -1
tick_interval = -1
status_type = STATUS_EFFECT_UNIQUE
alert_type = null
var/total_damage = 0
@@ -202,6 +203,7 @@
/datum/status_effect/heldup
id = "heldup"
duration = -1
tick_interval = -1
status_type = STATUS_EFFECT_MULTIPLE
alert_type = /obj/screen/alert/status_effect/heldup
@@ -214,6 +216,7 @@
/datum/status_effect/holdup
id = "holdup"
duration = -1
tick_interval = -1
status_type = STATUS_EFFECT_UNIQUE
alert_type = /obj/screen/alert/status_effect/holdup

View File

@@ -31,7 +31,8 @@
var/obj/screen/alert/status_effect/A = owner.throw_alert(id, alert_type)
A.attached_effect = src //so the alert can reference us, if it needs to
linked_alert = A //so we can reference the alert, if we need to
START_PROCESSING(SSfastprocess, src)
if(duration > 0 || initial(tick_interval) > 0) //don't process if we don't care
START_PROCESSING(SSfastprocess, src)
return TRUE
/datum/status_effect/Destroy()

View File

@@ -1,3 +1,8 @@
#define AI_WIRE_NORMAL 0
#define AI_WIRE_DISABLED 1
#define AI_WIRE_HACKED 2
#define AI_WIRE_DISABLED_HACKED -1
/datum/wires/airlock
holder_type = /obj/machinery/door/airlock
proper_name = "Generic Airlock"
@@ -98,16 +103,11 @@
A.emergency = FALSE
A.update_icon()
if(WIRE_AI) // Pulse to disable WIRE_AI control for 10 ticks (follows same rules as cutting).
if(A.aiControlDisabled == 0)
A.aiControlDisabled = 1
else if(A.aiControlDisabled == -1)
A.aiControlDisabled = 2
sleep(10)
if(A)
if(A.aiControlDisabled == 1)
A.aiControlDisabled = 0
else if(A.aiControlDisabled == 2)
A.aiControlDisabled = -1
if(A.aiControlDisabled == AI_WIRE_NORMAL)
A.aiControlDisabled = AI_WIRE_DISABLED
else if(A.aiControlDisabled == AI_WIRE_DISABLED_HACKED)
A.aiControlDisabled = AI_WIRE_HACKED
addtimer(CALLBACK(A, /obj/machinery/door/airlock.proc/reset_ai_wire), 1 SECONDS)
if(WIRE_SHOCK) // Pulse to shock the door for 10 ticks.
if(!A.secondsElectrified)
A.set_electrified(MACHINE_DEFAULT_ELECTRIFY_TIME, usr)
@@ -121,6 +121,12 @@
A.lights = !A.lights
A.update_icon()
/obj/machinery/door/airlock/proc/reset_ai_wire()
if(aiControlDisabled == AI_WIRE_DISABLED)
aiControlDisabled = AI_WIRE_NORMAL
else if(aiControlDisabled == AI_WIRE_HACKED)
aiControlDisabled = AI_WIRE_DISABLED_HACKED
/datum/wires/airlock/on_cut(wire, mend)
var/obj/machinery/door/airlock/A = holder
switch(wire)
@@ -147,15 +153,15 @@
A.bolt()
if(WIRE_AI) // Cut to disable WIRE_AI control, mend to re-enable.
if(mend)
if(A.aiControlDisabled == 1) // 0 = normal, 1 = locked out, 2 = overridden by WIRE_AI, -1 = previously overridden by WIRE_AI
A.aiControlDisabled = 0
else if(A.aiControlDisabled == 2)
A.aiControlDisabled = -1
if(A.aiControlDisabled == AI_WIRE_DISABLED) // 0 = normal, 1 = locked out, 2 = overridden by WIRE_AI, -1 = previously overridden by WIRE_AI
A.aiControlDisabled = AI_WIRE_NORMAL
else if(A.aiControlDisabled == AI_WIRE_HACKED)
A.aiControlDisabled = AI_WIRE_DISABLED_HACKED
else
if(A.aiControlDisabled == 0)
A.aiControlDisabled = 1
else if(A.aiControlDisabled == -1)
A.aiControlDisabled = 2
if(A.aiControlDisabled == AI_WIRE_NORMAL)
A.aiControlDisabled = AI_WIRE_DISABLED
else if(A.aiControlDisabled == AI_WIRE_DISABLED_HACKED)
A.aiControlDisabled = AI_WIRE_HACKED
if(WIRE_SHOCK) // Cut to shock the door, mend to unshock.
if(mend)
if(A.secondsElectrified)

View File

@@ -152,8 +152,7 @@
var/datum/component/slippery/slipper = GetComponent(/datum/component/slippery)
slipper.Slip(src, hit_atom)
if(thrownby && !caught)
sleep(1)
throw_at(thrownby, throw_range+2, throw_speed, null, TRUE)
addtimer(CALLBACK(src, /atom/movable.proc/throw_at, thrownby, throw_range+2, throw_speed, null, TRUE), 1)
else
return ..()

View File

@@ -60,7 +60,7 @@
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN
var/security_level = 0 //How much are wires secured
var/aiControlDisabled = 0 //If 1, AI control is disabled until the AI hacks back in and disables the lock. If 2, the AI has bypassed the lock. If -1, the control is enabled but the AI had bypassed it earlier, so if it is disabled again the AI would have no trouble getting back in.
var/aiControlDisabled = AI_WIRE_NORMAL //If 1, AI control is disabled until the AI hacks back in and disables the lock. If 2, the AI has bypassed the lock. If -1, the control is enabled but the AI had bypassed it earlier, so if it is disabled again the AI would have no trouble getting back in.
var/hackProof = FALSE // if true, this door can't be hacked by the AI
var/secondsMainPowerLost = 0 //The number of seconds until power is restored.
var/secondsBackupPowerLost = 0 //The number of seconds until power is restored.
@@ -475,10 +475,10 @@
return FALSE
/obj/machinery/door/airlock/proc/canAIControl(mob/user)
return ((aiControlDisabled != 1) && !isAllPowerCut())
return ((aiControlDisabled != AI_WIRE_DISABLED) && !isAllPowerCut())
/obj/machinery/door/airlock/proc/canAIHack()
return ((aiControlDisabled==1) && (!hackProof) && (!isAllPowerCut()));
return ((aiControlDisabled==AI_WIRE_DISABLED) && (!hackProof) && (!isAllPowerCut()));
/obj/machinery/door/airlock/hasPower()
return ((!secondsMainPowerLost || !secondsBackupPowerLost) && !(stat & NOPOWER))
@@ -835,7 +835,7 @@
to_chat(user, "Transfer complete. Forcing airlock to execute program.")
sleep(50)
//disable blocked control
aiControlDisabled = 2
aiControlDisabled = AI_WIRE_HACKED
to_chat(user, "Receiving control information from airlock.")
sleep(10)
//bring up airlock dialog

View File

@@ -437,7 +437,7 @@
damage_deflection = 30
explosion_block = 3
hackProof = TRUE
aiControlDisabled = 1
aiControlDisabled = AI_WIRE_DISABLED
normal_integrity = 700
security_level = 1
@@ -452,7 +452,7 @@
overlays_file = 'icons/obj/doors/airlocks/cult/runed/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_cult
hackProof = TRUE
aiControlDisabled = 1
aiControlDisabled = AI_WIRE_DISABLED
req_access = list(ACCESS_BLOODCULT)
damage_deflection = 10
var/openingoverlaytype = /obj/effect/temp_visual/cult/door
@@ -557,7 +557,7 @@
overlays_file = 'icons/obj/doors/airlocks/clockwork/overlays.dmi'
anim_parts = "left=-13,0;right=13,0"
hackProof = TRUE
aiControlDisabled = 1
aiControlDisabled = AI_WIRE_DISABLED
req_access = list(ACCESS_CLOCKCULT)
use_power = FALSE
resistance_flags = FIRE_PROOF | ACID_PROOF

View File

@@ -78,9 +78,11 @@
controller.cycleClose(door)
else
controller.onlyClose(door)
sleep(20)
busy = FALSE
update_icon()
addtimer(CALLBACK(src, .proc/not_busy), 2 SECONDS)
/obj/machinery/doorButtons/access_button/proc/not_busy()
busy = FALSE
update_icon()
/obj/machinery/doorButtons/access_button/update_icon()
if(stat & NOPOWER)

View File

@@ -25,11 +25,12 @@
random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6")
mergeable_decal = FALSE
/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions)
/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions, mapload=FALSE)
set waitfor = 0
var/direction = pick(directions)
for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++)
sleep(2)
if (!mapload)
sleep(2)
if(i > 0)
new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc)
if(!step_to(src, get_step(src, direction), 0))

View File

@@ -77,13 +77,14 @@
playsound(loc, 'sound/effects/gib_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 20 : 50, TRUE)
. = ..()
/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions)
/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions, mapload=FALSE)
set waitfor = FALSE
var/list/diseases = list()
SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases)
var/direction = pick(directions)
for(var/i in 0 to pick(0, 200; 1, 150; 2, 50))
sleep(2)
if (!mapload)
sleep(2)
if(i > 0)
new /obj/effect/decal/cleanable/blood/splatter(loc, diseases)
if(!step_to(src, get_step(src, direction), 0))

View File

@@ -11,15 +11,16 @@
bloodiness = BLOOD_AMOUNT_PER_DECAL
mergeable_decal = FALSE
/obj/effect/decal/cleanable/robot_debris/proc/streak(list/directions)
/obj/effect/decal/cleanable/robot_debris/proc/streak(list/directions, mapload=FALSE)
set waitfor = 0
var/direction = pick(directions)
for (var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++)
sleep(2)
if (!mapload)
sleep(2)
if (i > 0)
if (prob(40))
new /obj/effect/decal/cleanable/oil/streak(src.loc)
else if (prob(10))
else if (prob(10) && !mapload)
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
s.set_up(3, 1, src)
s.start()

View File

@@ -45,4 +45,4 @@
var/turf/T = loc
if(!istype(T)) //you know this will happen somehow
CRASH("Turf decal initialized in an object/nullspace")
T.AddComponent(/datum/component/decal, icon, icon_state, dir, CLEAN_GOD, color, null, null, alpha)
T.AddComponent(/datum/component/decal, icon, icon_state, dir, CLEAN_NEVER, color, null, null, alpha)

View File

@@ -57,7 +57,7 @@
var/list/directions = gibdirections[i]
if(isturf(loc))
if(directions.len)
gib.streak(directions)
gib.streak(directions, mapload)
return INITIALIZE_HINT_QDEL

View File

@@ -133,5 +133,4 @@
if(trigger_item && istype(AM, specific_item) && !claimed)
claimed = TRUE
flick("laserbox_burn", AM)
sleep(15)
qdel(AM)
QDEL_IN(AM, 15)

View File

@@ -25,6 +25,12 @@ GLOBAL_VAR(restart_counter)
*/
/world/New()
enable_debugger() //This does nothing if you aren't trying to debug
//Early profile for auto-profiler - will be stopped on profiler init if necessary.
#if DM_VERSION >= 513 && DM_BUILD >= 1506
world.Profile(PROFILE_START)
#endif
log_world("World loaded at [time_stamp()]!")
SetupExternalRSC()

View File

@@ -164,13 +164,9 @@
if((temperature < FIRE_MINIMUM_TEMPERATURE_TO_EXIST) || (volume <= 1))
qdel(src)
return
if(!location.air || (INSUFFICIENT(/datum/gas/plasma) && INSUFFICIENT(/datum/gas/tritium)) || INSUFFICIENT(/datum/gas/oxygen))
qdel(src)
return
//Not enough to burn
// god damn it previous coder you made the INSUFFICIENT macro for a fucking reason why didn't you use it here smh
if((INSUFFICIENT(/datum/gas/plasma) && INSUFFICIENT(/datum/gas/tritium)) || INSUFFICIENT(/datum/gas/oxygen))
//Not enough / nothing to burn
if(!location.air || (INSUFFICIENT(/datum/gas/plasma) && INSUFFICIENT(/datum/gas/tritium)) || INSUFFICIENT(/datum/gas/oxygen))
qdel(src)
return

View File

@@ -63,6 +63,8 @@
/obj/item/twohanded/required/kinetic_crusher/attack(mob/living/target, mob/living/carbon/user)
var/datum/status_effect/crusher_damage/C = target.has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
if(!C)
C = target.apply_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
var/target_health = target.health
..()
for(var/t in trophies)
@@ -97,6 +99,8 @@
if(!CM || CM.hammer_synced != src || !L.remove_status_effect(STATUS_EFFECT_CRUSHERMARK))
return
var/datum/status_effect/crusher_damage/C = L.has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
if(!C)
C = L.apply_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
var/target_health = L.health
for(var/t in trophies)
var/obj/item/crusher_trophy/T = t

View File

@@ -402,7 +402,7 @@
switch(rand(1,100)+modifier) //91-100=Nothing special happens
if(-INFINITY to 0) //attack yourself
I.attack(src,src)
INVOKE_ASYNC(I, /obj/item.proc/attack, src, src)
if(1 to 30) //throw it at yourself
I.throw_impact(src)
if(31 to 60) //Throw object in facing direction

View File

@@ -245,7 +245,7 @@
if(!illusion && (shock_damage * siemens_coeff >= 1) && prob(25))
set_heartattack(FALSE)
revive()
emote("gasp")
INVOKE_ASYNC(src, .proc/emote, "gasp")
Jitter(100)
SEND_SIGNAL(src, COMSIG_LIVING_MINOR_SHOCK)
adjustOrganLoss(ORGAN_SLOT_BRAIN, 100, 199) //yogs end

View File

@@ -6,7 +6,7 @@
losebreath = 0
if(!gibbed)
emote("deathgasp")
INVOKE_ASYNC(src, .proc/emote, "deathgasp")
. = ..()

View File

@@ -98,7 +98,7 @@
if(losebreath >= 1) //You've missed a breath, take oxy damage
losebreath--
if(prob(10))
emote("gasp")
INVOKE_ASYNC(src, .proc/emote, "gasp")
if(istype(loc, /obj/))
var/obj/loc_as_obj = loc
loc_as_obj.handle_internal_lifeform(src,0)
@@ -172,7 +172,7 @@
//OXYGEN
if(O2_partialpressure < safe_oxy_min) //Not enough oxygen
if(prob(20))
emote("gasp")
INVOKE_ASYNC(src, .proc/emote, "gasp")
if(O2_partialpressure > 0)
var/ratio = 1 - O2_partialpressure/safe_oxy_min
adjustOxyLoss(min(5*ratio, 3))

View File

@@ -39,7 +39,7 @@
if(prob(1))
to_chat(src, "<span class='danger'>You mutate!</span>")
easy_randmut(NEGATIVE+MINOR_NEGATIVE)
emote("gasp")
INVOKE_ASYNC(src, .proc/emote, "gasp")
domutcheck()
if(radiation > RAD_MOB_MUTATE * 2 && prob(50))

View File

@@ -33,7 +33,7 @@
/mob/living/simple_animal/bot/secbot/grievous/Initialize()
. = ..()
weapon = new baton_type(src)
weapon.attack_self(src)
INVOKE_ASYNC(weapon, /obj/item.proc/attack_self, src)
/mob/living/simple_animal/bot/secbot/grievous/Destroy()
QDEL_NULL(weapon)

View File

@@ -92,7 +92,7 @@
var/robot_arm = /obj/item/bodypart/r_arm/robot
var/commissioned = FALSE // Will other (noncommissioned) bots salute this bot?
var/can_salute = TRUE
COOLDOWN_DECLARE(next_salute_check)
var/salute_delay = 60 SECONDS
hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_PATH_HUD = HUD_LIST_LIST) //Diagnostic HUD views
@@ -257,12 +257,11 @@
if(!on || client)
return
if(!commissioned && can_salute)
for(var/mob/living/simple_animal/bot/B in get_hearers_in_view(5, get_turf(src)))
if(B.commissioned)
visible_message("<b>[src]</b> performs an elaborate salute for [B]!")
can_salute = FALSE
addtimer(VARSET_CALLBACK(src, can_salute, TRUE), salute_delay)
if(commissioned && COOLDOWN_FINISHED(src, next_salute_check))
COOLDOWN_START(src, next_salute_check, salute_delay)
for(var/mob/living/simple_animal/bot/B in view(5, src))
if(!B.commissioned && B.on)
visible_message("<b>[B]</b> performs an elaborate salute for [src]!")
break
switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command.

View File

@@ -305,7 +305,7 @@
/mob/living/simple_animal/pet/dog/corgi/proc/place_on_head(obj/item/item_to_add, mob/user)
if(istype(item_to_add, /obj/item/grenade/plastic)) // last thing he ever wears, I guess
item_to_add.afterattack(src,user,1)
INVOKE_ASYNC(item_to_add, /obj/item.proc/afterattack, src, user, 1)
return
if(inventory_head)

View File

@@ -65,7 +65,7 @@
if(spawn_mecha_type)
var/obj/mecha/M = new spawn_mecha_type (get_turf(src))
if(istype(M))
enter_mecha(M)
INVOKE_ASYNC(src, .proc/enter_mecha, M)
/mob/living/simple_animal/hostile/syndicate/mecha_pilot/proc/enter_mecha(obj/mecha/M)

View File

@@ -45,7 +45,6 @@
. = ..()
if(internal_type && true_spawn)
internal = new internal_type(src)
apply_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
ADD_TRAIT(src, TRAIT_NO_TELEPORT, MEGAFAUNA_TRAIT)
for(var/action_type in attack_action_types)
var/datum/action/innate/megafauna_attack/attack_action = new action_type()

View File

@@ -22,10 +22,6 @@
var/icon_aggro = null
var/crusher_drop_mod = 25
/mob/living/simple_animal/hostile/asteroid/Initialize(mapload)
. = ..()
apply_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
/mob/living/simple_animal/hostile/asteroid/Aggro()
..()
if(vision_range == aggro_vision_range && icon_aggro)

View File

@@ -407,7 +407,7 @@
update()
/obj/machinery/light/proc/broken_sparks(start_only=FALSE)
if(status == LIGHT_BROKEN && has_power())
if(status == LIGHT_BROKEN && has_power() && Master.current_runlevel)
if(!start_only)
do_sparks(3, TRUE, src)
var/delay = rand(BROKEN_SPARKS_MIN, BROKEN_SPARKS_MAX)

View File

@@ -5,6 +5,9 @@
var/modifier = 0
/datum/chemical_reaction/reagent_explosion/on_reaction(datum/reagents/holder, created_volume)
explode(holder, created_volume)
/datum/chemical_reaction/reagent_explosion/proc/explode(datum/reagents/holder, created_volume)
var/turf/T = get_turf(holder.my_atom)
var/inside_msg
if(ismob(holder.my_atom))
@@ -69,15 +72,16 @@
R.stun(20)
R.reveal(100)
R.adjustHealth(50)
sleep(20)
for(var/mob/living/carbon/C in get_hearers_in_view(round(created_volume/20,1),get_turf(holder.my_atom))) //roughly 5 tiles with 100/100 potwat
if(iscultist(C))
to_chat(C, "<span class='userdanger'>The divine explosion sears you!</span>")
C.Paralyze(40)
C.adjust_fire_stacks(5)
C.IgniteMob()
addtimer(CALLBACK(src, .proc/divine_explosion, round(created_volume/48,1),get_turf(holder.my_atom)), 2 SECONDS)
..()
/datum/chemical_reaction/reagent_explosion/potassium_explosion/holyboom/proc/divine_explosion(size, turf/T)
for(var/mob/living/carbon/C in get_hearers_in_view(size,T))
if(iscultist(C))
to_chat(C, "<span class='userdanger'>The divine explosion sears you!</span>")
C.Paralyze(40)
C.adjust_fire_stacks(5)
C.IgniteMob()
/datum/chemical_reaction/blackpowder
name = "Black Powder"
@@ -95,8 +99,7 @@
mix_message = "<span class='boldannounce'>Sparks start flying around the black powder!</span>"
/datum/chemical_reaction/reagent_explosion/blackpowder_explosion/on_reaction(datum/reagents/holder, created_volume)
sleep(rand(50,100))
..()
addtimer(CALLBACK(src, .proc/explode, holder, created_volume), rand(5,10) SECONDS)
/datum/chemical_reaction/thermite
name = "Thermite"
@@ -431,19 +434,22 @@
var/T1 = created_volume * 20 //100 units : Zap 3 times, with powers 2000/5000/12000. Tesla revolvers have a power of 10000 for comparison.
var/T2 = created_volume * 50
var/T3 = created_volume * 120
sleep(5)
var/added_delay = 0.5 SECONDS
if(created_volume >= 75)
tesla_zap(holder.my_atom, 7, T1, tesla_flags)
playsound(holder.my_atom, 'sound/machines/defib_zap.ogg', 50, 1)
sleep(15)
addtimer(CALLBACK(src, .proc/zappy_zappy, holder, T1), added_delay)
added_delay += 1.5 SECONDS
if(created_volume >= 40)
tesla_zap(holder.my_atom, 7, T2, tesla_flags)
playsound(holder.my_atom, 'sound/machines/defib_zap.ogg', 50, 1)
sleep(15)
addtimer(CALLBACK(src, .proc/zappy_zappy, holder, T2), added_delay)
added_delay += 1.5 SECONDS
if(created_volume >= 10) //10 units minimum for lightning, 40 units for secondary blast, 75 units for tertiary blast.
tesla_zap(holder.my_atom, 7, T3, tesla_flags)
playsound(holder.my_atom, 'sound/machines/defib_zap.ogg', 50, 1)
..()
addtimer(CALLBACK(src, .proc/zappy_zappy, holder, T3), added_delay)
addtimer(CALLBACK(src, .proc/explode, holder, created_volume), added_delay)
/datum/chemical_reaction/reagent_explosion/teslium_lightning/proc/zappy_zappy(datum/reagents/holder, power)
if(QDELETED(holder.my_atom))
return
tesla_zap(holder.my_atom, 7, power, tesla_flags)
playsound(holder.my_atom, 'sound/machines/defib_zap.ogg', 50, TRUE)
/datum/chemical_reaction/reagent_explosion/teslium_lightning/heat
id = "teslium_lightning2"

View File

@@ -3,6 +3,9 @@
var/deletes_extract = TRUE
/datum/chemical_reaction/slime/on_reaction(datum/reagents/holder)
use_slime_core(holder)
/datum/chemical_reaction/slime/proc/use_slime_core(datum/reagents/holder)
SSblackbox.record_feedback("tally", "slime_cores_used", 1, "type")
if(deletes_extract)
delete_extract(holder)
@@ -582,7 +585,9 @@
required_other = TRUE
/datum/chemical_reaction/slime/slimestop/on_reaction(datum/reagents/holder)
sleep(50)
addtimer(CALLBACK(src, .proc/slime_stop, holder), 5 SECONDS)
/datum/chemical_reaction/slime/slimestop/proc/slime_stop(datum/reagents/holder)
var/obj/item/slime_extract/sepia/extract = holder.my_atom
var/turf/T = get_turf(holder.my_atom)
new /obj/effect/timestop(T, null, null, null)
@@ -592,7 +597,7 @@
if(lastheld && !lastheld.equip_to_slot_if_possible(extract, SLOT_HANDS, disable_warning = TRUE))
extract.forceMove(get_turf(lastheld))
..()
use_slime_core(holder)
/datum/chemical_reaction/slime/slimecamera
name = "Slime Camera"

View File

@@ -60,6 +60,7 @@
/datum/status_effect/slimerecall
id = "slime_recall"
duration = -1 //Will be removed by the extract.
tick_interval = -1
alert_type = null
var/interrupted = FALSE
var/mob/target

View File

@@ -18,7 +18,7 @@
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)) //Damage the chest based on limb's existing damage
C.visible_message("<span class='danger'><B>[C]'s [src.name] has been violently dismembered!</B></span>")
C.emote("scream")
INVOKE_ASYNC(C, /mob.proc/emote, "scream")
SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "dismembered", /datum/mood_event/dismembered)
drop_limb()

View File

@@ -434,3 +434,5 @@ DEFAULT_VIEW_SQUARE 15x15
## 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
CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search
AUTO_PROFILE

View File

@@ -289,6 +289,7 @@
#include "code\controllers\subsystem\pathfinder.dm"
#include "code\controllers\subsystem\persistence.dm"
#include "code\controllers\subsystem\ping.dm"
#include "code\controllers\subsystem\profiler.dm"
#include "code\controllers\subsystem\radiation.dm"
#include "code\controllers\subsystem\radio.dm"
#include "code\controllers\subsystem\research.dm"