diff --git a/_build_dependencies.sh b/_build_dependencies.sh
index b251bfbe325..04dcb4aaa22 100644
--- a/_build_dependencies.sh
+++ b/_build_dependencies.sh
@@ -1,11 +1,11 @@
# This file has all the information on what versions of libraries are thrown into the code
# For dreamchecker
-export SPACEMAN_DMM_VERSION=suite-1.7
+export SPACEMAN_DMM_VERSION=suite-1.8
# For NanoUI + TGUI
export NODE_VERSION=16
# Byond Major
-export BYOND_MAJOR=514
+export BYOND_MAJOR=515
# Byond Minor
-export BYOND_MINOR=1589
+export BYOND_MINOR=1630
# Macro Count
export MACRO_COUNT=4
diff --git a/code/ATMOSPHERICS/_atmos_setup.dm b/code/ATMOSPHERICS/_atmos_setup.dm
index a2735133e25..bd141a30b98 100644
--- a/code/ATMOSPHERICS/_atmos_setup.dm
+++ b/code/ATMOSPHERICS/_atmos_setup.dm
@@ -6,7 +6,7 @@
// atmospherics devices.
//--------------------------------------------
-var/global/list/pipe_colors = list("grey" = PIPE_COLOR_GREY, "red" = PIPE_COLOR_RED, "blue" = PIPE_COLOR_BLUE, "cyan" = PIPE_COLOR_CYAN, "green" = PIPE_COLOR_GREEN, "yellow" = PIPE_COLOR_YELLOW, "black" = PIPE_COLOR_BLACK, "purple" = PIPE_COLOR_PURPLE)
+var/global/list/pipe_colors = list("grey" = PIPE_COLOR_GREY, "red" = PIPE_COLOR_RED, "blue" = PIPE_COLOR_BLUE, "cyan" = PIPE_COLOR_CYAN, "green" = PIPE_COLOR_GREEN, "yellow" = PIPE_COLOR_YELLOW, "black" = PIPE_COLOR_BLACK, "orange" = PIPE_COLOR_ORANGE, "white" = PIPE_COLOR_WHITE, "purple" = PIPE_COLOR_PURPLE)
/proc/pipe_color_lookup(var/color)
for(var/C in pipe_colors)
diff --git a/code/ATMOSPHERICS/atmospherics.dm b/code/ATMOSPHERICS/atmospherics.dm
index e5765075c25..27457567120 100644
--- a/code/ATMOSPHERICS/atmospherics.dm
+++ b/code/ATMOSPHERICS/atmospherics.dm
@@ -16,7 +16,7 @@ Pipelines + Other Objects -> Pipe network
power_channel = ENVIRON
var/nodealert = 0
var/power_rating //the maximum amount of power the machine can use to do work, affects how powerful the machine is, in Watts
-
+
unacidable = TRUE
layer = ATMOS_LAYER
plane = PLATING_PLANE
@@ -171,6 +171,9 @@ Pipelines + Other Objects -> Pipe network
if(construction_type)
var/obj/item/pipe/I = new construction_type(loc, null, null, src)
I.setPipingLayer(piping_layer)
+ if(istype(I, /obj/item/pipe/trinary/flippable))
+ var/obj/item/pipe/trinary/flippable/flip = I
+ flip.icon_state = "[flip.icon_state][flip.mirrored ? "m" : ""]"
transfer_fingerprints_to(I)
qdel(src)
diff --git a/code/ATMOSPHERICS/components/omni_devices/filter.dm b/code/ATMOSPHERICS/components/omni_devices/filter.dm
index 1b51693389b..8990ba011f3 100644
--- a/code/ATMOSPHERICS/components/omni_devices/filter.dm
+++ b/code/ATMOSPHERICS/components/omni_devices/filter.dm
@@ -131,8 +131,8 @@
if(portData.len)
data["ports"] = portData
if(output)
- data["set_flow_rate"] = round(set_flow_rate*10) //because nanoui can't handle rounded decimals.
- data["last_flow_rate"] = round(last_flow_rate*10)
+ data["set_flow_rate"] = round(set_flow_rate)
+ data["last_flow_rate"] = round(last_flow_rate)
return data
@@ -154,7 +154,7 @@
/obj/machinery/atmospherics/omni/atmos_filter/tgui_act(action, params)
if(..())
return TRUE
-
+
switch(action)
if("power")
if(!configuring)
@@ -266,4 +266,4 @@
else
initialize_directions |= P.dir
P.connect()
- P.update = 1
\ No newline at end of file
+ P.update = 1
diff --git a/code/ATMOSPHERICS/components/omni_devices/mixer.dm b/code/ATMOSPHERICS/components/omni_devices/mixer.dm
index 3487482be3c..fa627ddc64b 100644
--- a/code/ATMOSPHERICS/components/omni_devices/mixer.dm
+++ b/code/ATMOSPHERICS/components/omni_devices/mixer.dm
@@ -158,8 +158,8 @@
if(portData.len)
data["ports"] = portData
if(output)
- data["set_flow_rate"] = round(set_flow_rate*10) //because nanoui can't handle rounded decimals.
- data["last_flow_rate"] = round(last_flow_rate*10)
+ data["set_flow_rate"] = round(set_flow_rate)
+ data["last_flow_rate"] = round(last_flow_rate)
return data
@@ -294,4 +294,4 @@
/obj/machinery/atmospherics/omni/mixer/proc/con_lock(var/port = NORTH)
for(var/datum/omni_port/P in inputs)
if(P.dir == port)
- P.con_lock = !P.con_lock
\ No newline at end of file
+ P.con_lock = !P.con_lock
diff --git a/code/__byond_version_compat.dm b/code/__byond_version_compat.dm
index 086360e5e2e..6604aea70ba 100644
--- a/code/__byond_version_compat.dm
+++ b/code/__byond_version_compat.dm
@@ -1,8 +1,3 @@
-#if DM_VERSION >= 515
-#error PLEASE MAKE SURE THAT 515 IS PROPERLY TESTED AND WORKS. ESPECIALLY THE SAVE-FILES HAVE TO WORK.
-#error Additionally: Make sure that the GitHub Workflow was updated to BYOND 515 as well.
-#endif
-
// These defines are from __513_compatibility.dm -- Please Sort
#define CLAMP(CLVALUE, CLMIN, CLMAX) clamp(CLVALUE, CLMIN, CLMAX)
#define TAN(x) tan(x)
diff --git a/code/__defines/admin_vr.dm b/code/__defines/admin_vr.dm
index 83eb907f3c4..40e408d3ed7 100644
--- a/code/__defines/admin_vr.dm
+++ b/code/__defines/admin_vr.dm
@@ -16,5 +16,7 @@
#define MODIFIY_ROBOT_SWAP_MODULE "Swap a Robot Module"
#define MODIFIY_ROBOT_RESET_MODULE "Fully Reset Robot Module"
#define MODIFIY_ROBOT_TOGGLE_ERT "Toggle ERT Module Overwrite"
+#define MODIFIY_ROBOT_LIMIT_MODULES_ADD "Restrict Modules to"
+#define MODIFIY_ROBOT_LIMIT_MODULES_REMOVE "Remove from restricted Modules"
#define MODIFIY_ROBOT_TOGGLE_STATION_ACCESS "Toggle All Station Access Codes"
#define MODIFIY_ROBOT_TOGGLE_CENT_ACCESS "Toggle Central Access Codes"
diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm
index 06fb819b075..ad7c9b4f346 100644
--- a/code/__defines/dcs/signals.dm
+++ b/code/__defines/dcs/signals.dm
@@ -779,3 +779,20 @@
#define ELEMENT_CONFLICT_FOUND (1<<0)
//From reagents touch_x.
#define COMSIG_REAGENTS_TOUCH "reagent_touch"
+
+
+//Moved observer stuff to DCS
+#define COMSIG_OBSERVER_MOVED "observer_move"
+#define COMSIG_OBSERVER_DESTROYED "observer_destroyed"
+#define COMSIG_OBSERVER_SHUTTLE_ADDED "observer_shuttle_added"
+#define COMSIG_OBSERVER_SHUTTLE_PRE_MOVE "observer_shuttle_premove"
+#define COMSIG_OBSERVER_SHUTTLE_MOVED "observer_shuttle_moved"
+#define COMSIG_OBSERVER_TURF_ENTERED "observer_turf_entered"
+#define COMSIG_OBSERVER_TURF_EXITED "observer_turf_exited"
+#define COMSIG_OBSERVER_Z_MOVED "observer_z_moved"
+#define COMSIG_OBSERVER_MOB_EQUIPPED "observer_mob_equipped"
+#define COMSIG_OBSERVER_ITEM_EQUIPPED "observer_item_equipped"
+#define COMSIG_OBSERVER_MOB_UNEQUIPPED "observer_mob_unequipped"
+#define COMSIG_OBSERVER_ITEM_UNEQUIPPED "observer_item_unequipped"
+#define COMSIG_OBSERVER_APC "observer_apc"
+#define COMSIG_OBSERVER_GLOBALMOVED "observer_global_move"
diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index 3c303d18ad0..2f9337a2152 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -8,14 +8,15 @@
#define INVISIBILITY_LIGHTING 20
#define INVISIBILITY_LEVEL_ONE 35
#define INVISIBILITY_LEVEL_TWO 45
-#define INVISIBILITY_SHADEKIN 55 //CHOMPStation change
+#define INVISIBILITY_SHADEKIN 55
#define INVISIBILITY_OBSERVER 60
#define INVISIBILITY_EYE 61
#define SEE_INVISIBLE_LIVING 25
-#define SEE_INVISIBLE_NOLIGHTING 15
+#define SEE_INVISIBLE_NOLIGHTING 15
#define SEE_INVISIBLE_LEVEL_ONE 35
#define SEE_INVISIBLE_LEVEL_TWO 45
+#define SEE_INVISIBILITY_SHADEKIN 55
#define SEE_INVISIBLE_CULT 60
#define SEE_INVISIBLE_OBSERVER 61
diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index b8457de50bd..3bb8ebe6b0d 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -27,6 +27,7 @@
#define BORGTHERM 0x2
#define BORGXRAY 0x4
#define BORGMATERIAL 0x8
+#define BORGANOMALOUS 0x10
#define STANCE_ATTACK 11 // Backwards compatability
#define STANCE_ATTACKING 12 // Ditto
diff --git a/code/__defines/qdel.dm b/code/__defines/qdel.dm
index cf1afe492e7..7d815fe0f3d 100644
--- a/code/__defines/qdel.dm
+++ b/code/__defines/qdel.dm
@@ -1,4 +1,6 @@
-//defines that give qdel hints. these can be given as a return in destory() or by calling
+//! Defines that give qdel hints.
+//!
+//! These can be given as a return in [/atom/proc/Destroy] or by calling [/proc/qdel].
/// `qdel` should queue the object for deletion.
#define QDEL_HINT_QUEUE 0
@@ -11,41 +13,56 @@
// Qdel should assume this object won't gc, and hard delete it posthaste.
#define QDEL_HINT_HARDDEL_NOW 4
+
#ifdef REFERENCE_TRACKING
/** If REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb.
*
* Functionally identical to [QDEL_HINT_QUEUE] if [GC_FAILURE_HARD_LOOKUP] is not enabled in _compiler_options.dm.
*/
+#warn TG0001 qdel REFERENCE_TRACKING enabled
#define QDEL_HINT_FINDREFERENCE 5
/// Behavior as [QDEL_HINT_FINDREFERENCE], but only if the GC fails and a hard delete is forced.
#define QDEL_HINT_IFFAIL_FINDREFERENCE 6
#endif
-#define GC_QUEUE_CHECK 1
-#define GC_QUEUE_HARDDELETE 2
-#define GC_QUEUE_COUNT 2 //increase this when adding more steps.
+// Defines for the ssgarbage queues
+#define GC_QUEUE_FILTER 1 //! short queue to filter out quick gc successes so they don't hang around in the main queue for 5 minutes
+#define GC_QUEUE_CHECK 2 //! main queue that waits 5 minutes because thats the longest byond can hold a reference to our shit.
+#define GC_QUEUE_HARDDELETE 3 //! short queue for things that hard delete instead of going thru the gc subsystem, this is purely so if they *can* softdelete, they will soft delete rather then wasting time with a hard delete.
+#define GC_QUEUE_COUNT 3 //! Number of queues, used for allocating the nested lists. Don't forget to increase this if you add a new queue stage
+
+
+// Defines for the ssgarbage queue items
+#define GC_QUEUE_ITEM_QUEUE_TIME 1 //! Time this item entered the queue
+#define GC_QUEUE_ITEM_REF 2 //! Ref to the item
+#define GC_QUEUE_ITEM_GCD_DESTROYED 3 //! Item's gc_destroyed var value. Used to detect ref reuse.
+#define GC_QUEUE_ITEM_INDEX_COUNT 3 //! Number of item indexes, used for allocating the nested lists. Don't forget to increase this if you add a new queue item index
+
+// Defines for the time an item has to get its reference cleaned before it fails the queue and moves to the next.
+#define GC_FILTER_QUEUE (1 SECONDS)
+#define GC_CHECK_QUEUE (5 MINUTES)
+#define GC_DEL_QUEUE (10 SECONDS)
+
#define QDEL_ITEM_ADMINS_WARNED (1<<0) //! Set when admins are told about lag causing qdels in this type.
#define QDEL_ITEM_SUSPENDED_FOR_LAG (1<<1) //! Set when a type can no longer be hard deleted on failure because of lag it causes while this happens.
// Defines for the [gc_destroyed][/datum/var/gc_destroyed] var.
-#define GC_QUEUED_FOR_QUEUING -1
#define GC_CURRENTLY_BEING_QDELETED -2
#define QDELING(X) (X.gc_destroyed)
-#define QDELETED(X) (!X || X.gc_destroyed)
+#define QDELETED(X) (isnull(X) || QDELING(X))
#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
-//Qdel helper macros.
-#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), item), time, TIMER_STOPPABLE)
+// This is a bit hacky, we do it to avoid people relying on a return value for the macro
+// If you need that you should use QDEL_IN_STOPPABLE instead
+#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), (time) > GC_FILTER_QUEUE ? WEAKREF(item) : item), time);
+#define QDEL_IN_STOPPABLE(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), (time) > GC_FILTER_QUEUE ? WEAKREF(item) : item), time, TIMER_STOPPABLE)
#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME)
-#define QDEL_NULL(item) if(item) {qdel(item); item = null}
+#define QDEL_NULL(item) qdel(item); item = null
#define QDEL_NULL_LIST QDEL_LIST_NULL
#define QDEL_LIST_NULL(x) if(x) { for(var/y in x) { qdel(y) } ; x = null }
#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); }
#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(______qdel_list_wrapper), L), time, TIMER_STOPPABLE)
#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); }
#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); }
-
-/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly.
- QDEL_LIST(L)
diff --git a/code/__defines/rust_g_yw.dm b/code/__defines/rust_g_yw.dm
index 7d5a88aa65a..7ae22bb7f8b 100644
--- a/code/__defines/rust_g_yw.dm
+++ b/code/__defines/rust_g_yw.dm
@@ -1,3 +1,3 @@
// savefile ser/de //
-#define rustg_savefile_to_json(save_string) call(RUST_G, "savefile_to_json")(save_string)
+#define rustg_savefile_to_json(save_string) LIBCALL(RUST_G, "savefile_to_json")(save_string)
diff --git a/code/__defines/span_vr.dm b/code/__defines/span_vr.dm
index eaf45491cd3..49e4e0ef69d 100644
--- a/code/__defines/span_vr.dm
+++ b/code/__defines/span_vr.dm
@@ -52,6 +52,12 @@
#define span_reflex_shoot(str) ("" + str + "")
+/* Vore messages */
+
+#define span_vdanger(str) ("" + str + "")
+#define span_vwarning(str) ("" + str + "")
+#define span_vnotice(str) ("" + str + "")
+
/* Languages */
#define span_alien(str) ("" + str + "")
diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index 6474a946ef1..84c6fbd8a7c 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -127,6 +127,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define FIRE_PRIORITY_PING 10
#define FIRE_PRIORITY_AI 10
#define FIRE_PRIORITY_GARBAGE 15
+#define FIRE_PRIORITY_ASSETS 20
#define FIRE_PRIORITY_ALARM 20
#define FIRE_PRIORITY_CHARSETUP 25
#define FIRE_PRIORITY_AIRFLOW 30
diff --git a/code/_global_vars/misc.dm b/code/_global_vars/misc.dm
index 6ece6be2d7f..e767e04d97a 100644
--- a/code/_global_vars/misc.dm
+++ b/code/_global_vars/misc.dm
@@ -1,9 +1,9 @@
GLOBAL_LIST_EMPTY(error_last_seen)
GLOBAL_LIST_EMPTY(error_cooldown)
-GLOBAL_DATUM_INIT(all_observable_events, /datum/all_observable_events, new) // This is a datum. It is not a list.
-GLOBAL_DATUM_INIT(destroyed_event, /decl/observ/destroyed, new())
+//GLOBAL_DATUM_INIT(all_observable_events, /datum/all_observable_events, new) // This is a datum. It is not a list.
+//GLOBAL_DATUM_INIT(destroyed_event, /decl/observ/destroyed, new())
GLOBAL_VAR_INIT(timezoneOffset, 0) // The difference betwen midnight (of the host computer) and 0 world.ticks.
-GLOBAL_VAR_INIT(TAB, " ")
\ No newline at end of file
+GLOBAL_VAR_INIT(TAB, " ")
diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm
index 76299d48797..26124773229 100644
--- a/code/_helpers/_lists.dm
+++ b/code/_helpers/_lists.dm
@@ -53,7 +53,7 @@
// atoms/items/objects can be pretty and whatnot
var/atom/A = item
if(output_icons && isicon(A.icon) && !ismob(A)) // mobs tend to have unusable icons
- item_str += "\icon[A][bicon(A)] "
+ item_str += "[bicon(A)] "
switch(determiners)
if(DET_NONE) item_str += A.name
if(DET_DEFINITE) item_str += "\the [A]"
diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm
index a160069be49..1d3f8ed8d4f 100644
--- a/code/_helpers/global_lists.dm
+++ b/code/_helpers/global_lists.dm
@@ -327,3 +327,32 @@ GLOBAL_LIST_EMPTY(mannequins)
*/
//Hexidecimal numbers
var/global/list/hexNums = list("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F")
+
+// Many global vars aren't GLOB type. This puts them there to be more easily inspected.
+GLOBAL_LIST_EMPTY(legacy_globals)
+
+/proc/populate_legacy_globals()
+ //Note: these lists cannot be changed to a new list anywhere in code!
+ //If they are, these will cause the old list to stay around!
+ //Check by searching for " =" in the entire codebase
+ GLOB.legacy_globals["player_list"] = player_list
+ GLOB.legacy_globals["mob_list"] = mob_list
+ GLOB.legacy_globals["human_mob_list"] = human_mob_list
+ GLOB.legacy_globals["silicon_mob_list"] = silicon_mob_list
+ GLOB.legacy_globals["ai_list"] = ai_list
+ GLOB.legacy_globals["living_mob_list"] = living_mob_list
+ GLOB.legacy_globals["dead_mob_list"] = dead_mob_list
+ GLOB.legacy_globals["observer_mob_list"] = observer_mob_list
+ GLOB.legacy_globals["listening_objects"] = listening_objects
+ GLOB.legacy_globals["cleanbot_reserved_turfs"] = cleanbot_reserved_turfs
+ GLOB.legacy_globals["cable_list"] = cable_list
+ GLOB.legacy_globals["landmarks_list"] = landmarks_list
+ GLOB.legacy_globals["event_triggers"] = event_triggers
+ GLOB.legacy_globals["side_effects"] = side_effects
+ GLOB.legacy_globals["mechas_list"] = mechas_list
+ GLOB.legacy_globals["mannequins_"] = mannequins_
+ //visual nets
+ GLOB.legacy_globals["visual_nets"] = visual_nets
+ GLOB.legacy_globals["cameranet"] = cameranet
+ GLOB.legacy_globals["cultnet"] = cultnet
+ GLOB.legacy_globals["existing_solargrubs"] = existing_solargrubs
diff --git a/code/_helpers/global_lists_vr.dm b/code/_helpers/global_lists_vr.dm
index 103862bfb1d..07553be0acb 100644
--- a/code/_helpers/global_lists_vr.dm
+++ b/code/_helpers/global_lists_vr.dm
@@ -240,6 +240,7 @@ var/global/list/edible_trash = list(/obj/item/broken_device,
/obj/item/weapon/storage/wallet,
/obj/item/weapon/storage/vore_egg,
/obj/item/weapon/bikehorn/tinytether,
+ /obj/item/weapon/entrepreneur,
/obj/item/capture_crystal,
/obj/item/roulette_ball,
/obj/item/pizzabox
diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm
index 414238fcaad..a81c09b314f 100644
--- a/code/_helpers/icons.dm
+++ b/code/_helpers/icons.dm
@@ -536,6 +536,13 @@ GLOBAL_LIST_EMPTY(cached_examine_icons)
CRASH("get_dummy_savefile failed to create a dummy savefile: '[error]'")
return get_dummy_savefile(from_failure = TRUE)
+
+/// Generate a filename for this asset
+/// The same asset will always lead to the same asset name
+/// (Generated names do not include file extention.)
+/proc/generate_asset_name(file)
+ return "asset.[md5(fcopy_rsc(file))]"
+
/**
* Converts an icon to base64. Operates by putting the icon in the iconCache savefile,
* exporting it as text, and then parsing the base64 from that.
@@ -655,12 +662,12 @@ GLOBAL_LIST_EMPTY(cached_examine_icons)
//var/name = SANITIZE_FILENAME("[generate_asset_name(thing)].png")
var/name = "[generate_asset_name(thing)].png"
if (!SSassets.cache[name])
- register_asset(name, thing)
+ SSassets.transport.register_asset(name, thing)
for (var/thing2 in targets)
- send_asset(thing2, name)
+ SSassets.transport.send_assets(thing2, name)
if(sourceonly)
- return get_asset_url(name)
- return ""
+ return SSassets.transport.get_asset_url(name)
+ return ""
//its either an atom, image, or mutable_appearance, we want its icon var
icon2collapse = thing.icon
@@ -697,12 +704,12 @@ GLOBAL_LIST_EMPTY(cached_examine_icons)
key = "[name_and_ref[3]].png"
if(!SSassets.cache[key])
- register_asset(key, rsc_ref, file_hash, icon_path)
+ SSassets.transport.register_asset(key, rsc_ref, file_hash, icon_path)
for (var/client_target in targets)
- send_asset(client_target, key)
+ SSassets.transport.send_assets(client_target, key)
if(sourceonly)
- return get_asset_url(key)
- return ""
+ return SSassets.transport.get_asset_url(key)
+ return ""
/proc/icon2base64html(target, var/custom_classes = "")
if (!target)
diff --git a/code/_helpers/sorts/TimSort.dm b/code/_helpers/sorts/TimSort.dm
index cfa55f0dfa3..f3b065f2fd9 100644
--- a/code/_helpers/sorts/TimSort.dm
+++ b/code/_helpers/sorts/TimSort.dm
@@ -8,10 +8,14 @@
if(toIndex <= 0)
toIndex += L.len + 1
- sortInstance.L = L
- sortInstance.cmp = cmp
- sortInstance.associative = associative
+ var/datum/sort_instance/SI = GLOB.sortInstance
+ if(!SI)
+ SI = new
- sortInstance.timSort(fromIndex, toIndex)
+ SI.L = L
+ SI.cmp = cmp
+ SI.associative = associative
+
+ SI.timSort(fromIndex, toIndex)
return L
diff --git a/code/_helpers/sorts/__main.dm b/code/_helpers/sorts/__main.dm
index 7fd0a505c70..41b5b7e7490 100644
--- a/code/_helpers/sorts/__main.dm
+++ b/code/_helpers/sorts/__main.dm
@@ -9,13 +9,13 @@
#define MIN_GALLOP 7
//This is a global instance to allow much of this code to be reused. The interfaces are kept separately
-var/datum/sortInstance/sortInstance = new()
-/datum/sortInstance
+GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
+/datum/sort_instance
//The array being sorted.
var/list/L
//The comparator proc-reference
- var/cmp = /proc/cmp_numeric_asc
+ var/cmp = GLOBAL_PROC_REF(cmp_numeric_asc)
//whether we are sorting list keys (0: L[i]) or associated values (1: L[L[i]])
var/associative = 0
@@ -32,7 +32,7 @@ var/datum/sortInstance/sortInstance = new()
var/list/runLens = list()
-/datum/sortInstance/proc/timSort(start, end)
+/datum/sort_instance/proc/timSort(start, end)
runBases.Cut()
runLens.Cut()
@@ -97,7 +97,7 @@ lo the index of the first element in the range to be sorted
hi the index after the last element in the range to be sorted
start the index of the first element in the range that is not already known to be sorted
*/
-/datum/sortInstance/proc/binarySort(lo, hi, start)
+/datum/sort_instance/proc/binarySort(lo, hi, start)
//ASSERT(lo <= start && start <= hi)
if(start <= lo)
start = lo + 1
@@ -135,7 +135,7 @@ For its intended use in a stable mergesort, the strictness of the
definition of "descending" is needed so that the call can safely
reverse a descending sequence without violating stability.
*/
-/datum/sortInstance/proc/countRunAndMakeAscending(lo, hi)
+/datum/sort_instance/proc/countRunAndMakeAscending(lo, hi)
//ASSERT(lo < hi)
var/runHi = lo + 1
@@ -165,7 +165,7 @@ reverse a descending sequence without violating stability.
//Returns the minimum acceptable run length for an array of the specified length.
//Natural runs shorter than this will be extended with binarySort
-/datum/sortInstance/proc/minRunLength(n)
+/datum/sort_instance/proc/minRunLength(n)
//ASSERT(n >= 0)
var/r = 0 //becomes 1 if any bits are shifted off
while(n >= MIN_MERGE)
@@ -178,7 +178,7 @@ reverse a descending sequence without violating stability.
// runLen[i-2] > runLen[i-1]
//This method is called each time a new run is pushed onto the stack.
//So the invariants are guaranteed to hold for i= 2)
var/n = runBases.len - 1
if(n > 1 && runLens[n-1] <= runLens[n] + runLens[n+1])
@@ -193,7 +193,7 @@ reverse a descending sequence without violating stability.
//Merges all runs on the stack until only one remains.
//Called only once, to finalise the sort
-/datum/sortInstance/proc/mergeForceCollapse()
+/datum/sort_instance/proc/mergeForceCollapse()
while(runBases.len >= 2)
var/n = runBases.len - 1
if(n > 1 && runLens[n-1] < runLens[n+1])
@@ -204,7 +204,7 @@ reverse a descending sequence without violating stability.
//Merges the two consecutive runs at stack indices i and i+1
//Run i must be the penultimate or antepenultimate run on the stack
//In other words, i must be equal to stackSize-2 or stackSize-3
-/datum/sortInstance/proc/mergeAt(i)
+/datum/sort_instance/proc/mergeAt(i)
//ASSERT(runBases.len >= 2)
//ASSERT(i >= 1)
//ASSERT(i == runBases.len - 1 || i == runBases.len - 2)
@@ -258,7 +258,7 @@ reverse a descending sequence without violating stability.
Returns the index at which to insert element 'key'
*/
-/datum/sortInstance/proc/gallopLeft(key, base, len, hint)
+/datum/sort_instance/proc/gallopLeft(key, base, len, hint)
//ASSERT(len > 0 && hint >= 0 && hint < len)
var/lastOffset = 0
@@ -317,7 +317,7 @@ reverse a descending sequence without violating stability.
* @param c the comparator used to order the range, and to search
* @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k]
*/
-/datum/sortInstance/proc/gallopRight(key, base, len, hint)
+/datum/sort_instance/proc/gallopRight(key, base, len, hint)
//ASSERT(len > 0 && hint >= 0 && hint < len)
var/offset = 1
@@ -369,7 +369,7 @@ reverse a descending sequence without violating stability.
//Merges two adjacent runs in-place in a stable fashion.
//For performance this method should only be called when len1 <= len2!
-/datum/sortInstance/proc/mergeLo(base1, len1, base2, len2)
+/datum/sort_instance/proc/mergeLo(base1, len1, base2, len2)
//ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2)
var/cursor1 = base1
@@ -471,7 +471,7 @@ reverse a descending sequence without violating stability.
//ASSERT(len1 > 1)
-/datum/sortInstance/proc/mergeHi(base1, len1, base2, len2)
+/datum/sort_instance/proc/mergeHi(base1, len1, base2, len2)
//ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2)
var/cursor1 = base1 + len1 - 1 //start at end of sublists
@@ -571,7 +571,7 @@ reverse a descending sequence without violating stability.
//ASSERT(len2 > 0)
-/datum/sortInstance/proc/mergeSort(start, end)
+/datum/sort_instance/proc/mergeSort(start, end)
var/remaining = end - start
//If array is small, do an insertion sort
@@ -616,7 +616,7 @@ reverse a descending sequence without violating stability.
return L
-/datum/sortInstance/proc/mergeAt2(i)
+/datum/sort_instance/proc/mergeAt2(i)
var/cursor1 = runBases[i]
var/cursor2 = runBases[i+1]
diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm
index 571bf281ce6..1cda5c811f0 100644
--- a/code/_helpers/text.dm
+++ b/code/_helpers/text.dm
@@ -348,10 +348,10 @@
return tagdesc
if(!text_tag_cache[tagname])
var/icon/tag = icon(text_tag_icons, tagname)
- text_tag_cache[tagname] = bicon(tag, TRUE, "text_tag")
+ text_tag_cache[tagname] = tag
if(!C.tgui_panel.is_ready() || C.tgui_panel.oldchat)
return ""
- return text_tag_cache[tagname]
+ return icon2html(text_tag_cache[tagname], C, extra_classes = "text_tag")
/proc/create_text_tag_old(var/tagname, var/tagdesc = tagname, var/client/C = null)
if(!(C && C.is_preference_enabled(/datum/client_preference/chat_tags)))
diff --git a/code/_helpers/type2type.dm b/code/_helpers/type2type.dm
index 1479a07d354..40be3d403ad 100644
--- a/code/_helpers/type2type.dm
+++ b/code/_helpers/type2type.dm
@@ -552,3 +552,9 @@
catch(var/exception/E)
if(error_on_invalid_return)
error("Exception when loading file as string: [E]")
+
+
+/// Return html to load a url.
+/// for use inside of browse() calls to html assets that might be loaded on a cdn.
+/proc/url2htmlloader(url)
+ return {""}
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index a3a8d563725..b39c71a7d23 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -58,9 +58,11 @@
#define ui_zonesel "EAST-1:28,SOUTH:5"
#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12)
-#define ui_borg_pull "EAST-3:24,SOUTH+1:7"
-#define ui_borg_module "EAST-2:26,SOUTH+1:7"
-#define ui_borg_panel "EAST-1:28,SOUTH+1:7"
+#define ui_borg_pull "EAST-4:24,SOUTH+1:7" //borgs
+#define ui_borg_radio "EAST-2:26,SOUTH+1:7" //borgs
+#define ui_borg_panel "EAST-1:28,SOUTH+1:7" //borgs
+#define ui_borg_module "EAST-3:24,SOUTH+1:5"//borgs
+#define ui_vtec_control "EAST-3:24,SOUTH:5"//borgs
#define ui_ai_core "SOUTH:6,WEST:16"
#define ui_ai_camera_list "SOUTH:6,WEST+1:16"
@@ -197,4 +199,4 @@
#define ui_mech_deco1_f "WEST+2:-7, SOUTH+8"
#define ui_mech_deco2_f "WEST+2:-7, SOUTH+9"
-#define ui_smallquad "EAST-4:22,SOUTH:5"
\ No newline at end of file
+#define ui_smallquad "EAST-4:22,SOUTH:5"
diff --git a/code/_onclick/hud/ability_screen_objects.dm b/code/_onclick/hud/ability_screen_objects.dm
index cb500b4fe3a..7690b9d2033 100644
--- a/code/_onclick/hud/ability_screen_objects.dm
+++ b/code/_onclick/hud/ability_screen_objects.dm
@@ -183,6 +183,13 @@
if(!ability_master) //VOREStation Edit: S H A D E K I N
ability_master = new /obj/screen/movable/ability_master(src)
+
+/mob/Destroy()
+ ..()
+ if(ability_master)
+ QDEL_NULL(ability_master)
+
+
///////////ACTUAL ABILITIES////////////
//This is what you click to do things//
///////////////////////////////////////
@@ -383,4 +390,4 @@
A.name = object_given.name
ability_objects.Add(A)
if(my_mob.client)
- toggle_open(2) //forces the icons to refresh on screen
\ No newline at end of file
+ toggle_open(2) //forces the icons to refresh on screen
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 2a997afba07..af265268462 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -176,6 +176,7 @@ var/list/global_huds = list(
var/obj/screen/l_hand_hud_object
var/obj/screen/action_intent
var/obj/screen/move_intent
+ var/obj/screen/control_vtec
var/list/adding
/// Misc hud elements that are hidden when the hud is minimized
@@ -219,6 +220,7 @@ var/list/global_huds = list(
l_hand_hud_object = null
action_intent = null
move_intent = null
+ control_vtec = null
adding = null
other = null
other_important = null
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index ae0f7f4c474..c9cd99ba45c 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -25,7 +25,7 @@ var/obj/screen/robot_inventory
using.color = HUD.ui_color
using.alpha = HUD.ui_alpha
using.icon_state = "radio"
- using.screen_loc = ui_movi
+ using.screen_loc = ui_borg_radio
using.layer = HUD_LAYER
adding += using
@@ -81,6 +81,17 @@ var/obj/screen/robot_inventory
adding += using
HUD.action_intent = using
+ //Move intent (walk/run)
+ using = new /obj/screen()
+ using.name = "mov_intent"
+ using.icon = HUD.ui_style
+ using.icon_state = (m_intent == "run" ? "running" : "walking")
+ using.screen_loc = ui_movi
+ using.color = HUD.ui_color
+ using.alpha = HUD.ui_alpha
+ HUD.adding += using
+ HUD.move_intent = using
+
//Health
healths = new /obj/screen()
healths.icon = HUD.ui_style
@@ -206,6 +217,32 @@ var/obj/screen/robot_inventory
client.screen += HUD.adding + HUD.other
client.screen += client.void
+/datum/hud/proc/toggle_vtec_control()
+ if(!isrobot(mymob))
+ return
+
+ var/mob/living/silicon/robot/R = mymob
+ if(!control_vtec)
+ var/obj/screen/using = new /obj/screen()
+ using.name = "control_vtec"
+ using.icon = ui_style
+ using.screen_loc = ui_vtec_control
+ using.color = ui_color
+ using.alpha = ui_alpha
+ control_vtec = using
+ if(R.vtec_active)
+ if(R.speed == 0)
+ control_vtec.icon_state = "speed_0"
+ else if(R.speed == -0.5)
+ control_vtec.icon_state = "speed_1"
+ else if(R.speed == -1)
+ control_vtec.icon_state = "speed_2"
+ R.m_intent = "run"
+ R.hud_used.move_intent.icon_state = "running"
+ R.client.screen += control_vtec
+ else
+ R.client.screen -= control_vtec
+ R.speed = 0
/datum/hud/proc/toggle_show_robot_modules()
if(!isrobot(mymob))
@@ -276,7 +313,7 @@ var/obj/screen/robot_inventory
else
//Modules display is hidden
//r.client.screen -= robot_inventory //"store" icon
- for(var/atom/A in r.module.modules)
+ for(var/atom/A in r.module?.modules)
if(r.client && (A != r.module_state_1) && (A != r.module_state_2) && (A != r.module_state_3) )
//Module is not currently active
r.client.screen -= A
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index dde89b98ab5..cd9c4044b6a 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -272,6 +272,19 @@
var/mob/living/L = usr
L.resist()
+ if("control_vtec")
+ if(isrobot(usr))
+ var/mob/living/silicon/robot/R = usr
+ if(R.speed == 0 && R.vtec_active)
+ R.speed = -0.5
+ R.hud_used.control_vtec.icon_state = "speed_1"
+ else if(R.speed == -0.5 && R.vtec_active)
+ R.speed = -1
+ R.hud_used.control_vtec.icon_state = "speed_2"
+ else
+ R.speed = 0
+ R.hud_used.control_vtec.icon_state = "speed_0"
+
if("mov_intent")
if(isliving(usr))
if(iscarbon(usr))
diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm
index f89d4001476..50906085047 100644
--- a/code/_onclick/observer.dm
+++ b/code/_onclick/observer.dm
@@ -24,7 +24,8 @@
ManualFollow(A)
// Otherwise jump
else
- following = null
+ if(following)
+ stop_following()
forceMove(get_turf(A))
/mob/observer/dead/ClickOn(var/atom/A, var/params)
@@ -68,9 +69,9 @@
// VOREStation Edit Begin
-/obj/machinery/gateway/centerstation/attack_ghost(mob/user as mob)
- if(awaygate)
- if(user.client.holder)
+/obj/machinery/gateway/centerstation/attack_ghost(mob/user as mob)
+ if(awaygate)
+ if(user.client.holder)
user.loc = awaygate.loc
else if(active)
user.loc = awaygate.loc
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index de8b45dd371..76214eb64dd 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -305,10 +305,24 @@ var/list/gamemode_cache = list()
var/static/invoke_youtubedl = null
+
+ var/static/asset_transport
+
+ var/static/cache_assets = FALSE
+
+ var/static/save_spritesheets = FALSE
+
+ var/static/asset_simple_preload = FALSE
+
+ var/static/asset_cdn_webroot
+
+ var/static/asset_cdn_url
+
//Enables/Disables the appropriate mob type from obtaining the verb on spawn. Still allows admins to manually give it to them.
var/static/allow_robot_recolor = FALSE
var/static/allow_simple_mob_recolor = FALSE
+
/datum/configuration/New()
var/list/L = subtypesof(/datum/game_mode)
for (var/T in L)
@@ -987,6 +1001,24 @@ var/list/gamemode_cache = list()
if("invoke_youtubedl")
config.invoke_youtubedl = value
+ if("asset_transport")
+ config.asset_transport = value
+
+ if("cache_assets")
+ config.cache_assets = TRUE
+
+ if("save_spritesheets")
+ config.save_spritesheets = TRUE
+
+ if("asset_simple_preload")
+ config.asset_simple_preload = TRUE
+
+ if("asset_cdn_webroot")
+ config.asset_cdn_webroot = value
+
+ if("asset_cdn_url")
+ config.asset_cdn_url = value
+
else
log_misc("Unknown setting in configuration: '[name]'")
diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm
index 6b7c0b41376..cd6e7544dd4 100644
--- a/code/controllers/globals.dm
+++ b/code/controllers/globals.dm
@@ -62,3 +62,5 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
var/end_tick = world.time
if(end_tick - start_tick)
warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!")
+
+ populate_legacy_globals()
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 13a0d5ebacd..e08c860d568 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -281,7 +281,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
//(higher subsystems will be sooner in the queue, adding them later in the loop means we don't have to loop thru them next queue add)
sortTim(tickersubsystems, GLOBAL_PROC_REF(cmp_subsystem_priority))
for(var/I in runlevel_sorted_subsystems)
- sortTim(runlevel_sorted_subsystems, GLOBAL_PROC_REF(cmp_subsystem_priority))
+ sortTim(I, GLOBAL_PROC_REF(cmp_subsystem_priority))
I += tickersubsystems
var/cached_runlevel = current_runlevel
diff --git a/code/controllers/subsystems/asset_loading.dm b/code/controllers/subsystems/asset_loading.dm
new file mode 100644
index 00000000000..2d2939de4b2
--- /dev/null
+++ b/code/controllers/subsystems/asset_loading.dm
@@ -0,0 +1,28 @@
+/// Allows us to lazyload asset datums
+/// Anything inserted here will fully load if directly gotten
+/// So this just serves to remove the requirement to load assets fully during init
+SUBSYSTEM_DEF(asset_loading)
+ name = "Asset Loading"
+ priority = FIRE_PRIORITY_ASSETS
+ flags = SS_NO_INIT
+ runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT
+ var/list/datum/asset/generate_queue = list()
+
+/datum/controller/subsystem/asset_loading/fire(resumed)
+ while(length(generate_queue))
+ var/datum/asset/to_load = generate_queue[generate_queue.len]
+
+ to_load.queued_generation()
+
+ if(MC_TICK_CHECK)
+ return
+ generate_queue.len--
+
+/datum/controller/subsystem/asset_loading/proc/queue_asset(datum/asset/queue)
+#ifdef DO_NOT_DEFER_ASSETS
+ stack_trace("We queued an instance of [queue.type] for lateloading despite not allowing it")
+#endif
+ generate_queue += queue
+
+/datum/controller/subsystem/asset_loading/proc/dequeue_asset(datum/asset/queue)
+ generate_queue -= queue
diff --git a/code/controllers/subsystems/assets.dm b/code/controllers/subsystems/assets.dm
index 522deae1776..49e8a8f5781 100644
--- a/code/controllers/subsystems/assets.dm
+++ b/code/controllers/subsystems/assets.dm
@@ -2,17 +2,39 @@ SUBSYSTEM_DEF(assets)
name = "Assets"
init_order = INIT_ORDER_ASSETS
flags = SS_NO_FIRE
- var/list/cache = list()
+ var/list/datum/asset_cache_item/cache = list()
var/list/preload = list()
+ var/datum/asset_transport/transport = new()
-/datum/controller/subsystem/assets/Initialize(timeofday)
- for(var/typepath in typesof(/datum/asset))
- var/datum/asset/A = typepath
- if (typepath != initial(A._abstract))
- get_asset_datum(typepath)
+/datum/controller/subsystem/assets/proc/OnConfigLoad()
+ var/newtransporttype = /datum/asset_transport
+ switch (config.asset_transport)
+ if ("webroot")
+ newtransporttype = /datum/asset_transport/webroot
- preload = cache.Copy() //don't preload assets generated during the round
+ if (newtransporttype == transport.type)
+ return
- for(var/client/C in GLOB.clients)
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(getFilesSlow), C, preload, FALSE), 10)
- return ..()
+ var/datum/asset_transport/newtransport = new newtransporttype ()
+ if (newtransport.validate_config())
+ transport = newtransport
+ transport.Load()
+
+
+
+/datum/controller/subsystem/assets/Initialize()
+ OnConfigLoad()
+
+ for(var/type in typesof(/datum/asset))
+ var/datum/asset/A = type
+ if (type != initial(A._abstract))
+ load_asset_datum(type)
+
+ transport.Initialize(cache)
+
+ subsystem_initialized = TRUE
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/assets/Recover()
+ cache = SSassets.cache
+ preload = SSassets.preload
diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm
index 400a61b0f94..a4bc5ee17d1 100644
--- a/code/controllers/subsystems/garbage.dm
+++ b/code/controllers/subsystems/garbage.dm
@@ -1,3 +1,26 @@
+/*!
+## Debugging GC issues
+
+In order to debug `qdel()` failures, there are several tools available.
+To enable these tools, define `TESTING` in [_compile_options.dm](https://github.com/tgstation/-tg-station/blob/master/code/_compile_options.dm).
+
+First is a verb called "Find References", which lists **every** refererence to an object in the world. This allows you to track down any indirect or obfuscated references that you might have missed.
+
+Complementing this is another verb, "qdel() then Find References".
+This does exactly what you'd expect; it calls `qdel()` on the object and then it finds all references remaining.
+This is great, because it means that `Destroy()` will have been called before it starts to find references,
+so the only references you'll find will be the ones preventing the object from `qdel()`ing gracefully.
+
+If you have a datum or something you are not destroying directly (say via the singulo),
+the next tool is `QDEL_HINT_FINDREFERENCE`. You can return this in `Destroy()` (where you would normally `return ..()`),
+to print a list of references once it enters the GC queue.
+
+Finally is a verb, "Show qdel() Log", which shows the deletion log that the garbage subsystem keeps. This is helpful if you are having race conditions or need to review the order of deletions.
+
+Note that for any of these tools to work `TESTING` must be defined.
+By using these methods of finding references, you can make your life far, far easier when dealing with `qdel()` failures.
+*/
+
SUBSYSTEM_DEF(garbage)
name = "Garbage"
priority = FIRE_PRIORITY_GARBAGE
@@ -5,8 +28,9 @@ SUBSYSTEM_DEF(garbage)
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
init_order = INIT_ORDER_GARBAGE
+// init_stage = INITSTAGE_EARLY
- var/list/collection_timeout = list(2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
+ var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_CHECK_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level
//Stat tracking
var/delslasttick = 0 // number of del()'s we've done this tick
@@ -26,18 +50,16 @@ SUBSYSTEM_DEF(garbage)
var/list/queues
#ifdef REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
+ #ifdef REFERENCE_TRACKING_DEBUG
+ //Should we save found refs. Used for unit testing
+ var/should_save_refs = FALSE
+ #endif
var/find_reference_on_fail_global_toggle = FALSE
#endif
/datum/controller/subsystem/garbage/PreInit()
- queues = new(GC_QUEUE_COUNT)
- pass_counts = new(GC_QUEUE_COUNT)
- fail_counts = new(GC_QUEUE_COUNT)
- for(var/i in 1 to GC_QUEUE_COUNT)
- queues[i] = list()
- pass_counts[i] = 0
- fail_counts[i] = 0
+ InitQueues()
/datum/controller/subsystem/garbage/stat_entry(msg)
var/list/counts = list()
@@ -61,39 +83,48 @@ SUBSYSTEM_DEF(garbage)
/datum/controller/subsystem/garbage/Shutdown()
//Adds the del() log to the qdel log file
- var/list/dellog = list()
+ var/list/del_log = list()
//sort by how long it's wasted hard deleting
sortTim(items, cmp=/proc/cmp_qdel_item_time, associative = TRUE)
for(var/path in items)
var/datum/qdel_item/I = items[path]
- dellog += "Path: [path]"
+ var/list/entry = list()
+ del_log[path] = entry
+
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
- dellog += "\tSUSPENDED FOR LAG"
+ entry["SUSPENDED FOR LAG"] = TRUE
if (I.failures)
- dellog += "\tFailures: [I.failures]"
- dellog += "\tqdel() Count: [I.qdels]"
- dellog += "\tDestroy() Cost: [I.destroy_time]ms"
+ entry["Failures"] = I.failures
+ entry["qdel() Count"] = I.qdels
+ entry["Destroy() Cost (ms)"] = I.destroy_time
+
if (I.hard_deletes)
- dellog += "\tTotal Hard Deletes: [I.hard_deletes]"
- dellog += "\tTime Spent Hard Deleting: [I.hard_delete_time]ms"
- dellog += "\tHighest Time Spent Hard Deleting: [I.hard_delete_max]ms"
+ entry["Total Hard Deletes"] = I.hard_deletes
+ entry["Time Spend Hard Deleting (ms)"] = I.hard_delete_time
+ entry["Highest Time Spend Hard Deleting (ms)"] = I.hard_delete_max
if (I.hard_deletes_over_threshold)
- dellog += "\tHard Deletes Over Threshold: [I.hard_deletes_over_threshold]"
+ entry["Hard Deletes Over Threshold"] = I.hard_deletes_over_threshold
if (I.slept_destroy)
- dellog += "\tSleeps: [I.slept_destroy]"
+ entry["Total Sleeps"] = I.slept_destroy
if (I.no_respect_force)
- dellog += "\tIgnored force: [I.no_respect_force] times"
+ entry["Total Ignored Force"] = I.no_respect_force
if (I.no_hint)
- dellog += "\tNo hint: [I.no_hint] times"
- text2file(dellog.Join(), "[log_path]-qdel.log")
+ entry["Total No Hint"] = I.no_hint
+ if(LAZYLEN(I.extra_details))
+ entry["Deleted Metadata"] = I.extra_details
+
+ log_debug("", del_log)
/datum/controller/subsystem/garbage/fire()
//the fact that this resets its processing each fire (rather then resume where it left off) is intentional.
- var/queue = GC_QUEUE_CHECK
+ var/queue = GC_QUEUE_FILTER
while (state == SS_RUNNING)
switch (queue)
+ if (GC_QUEUE_FILTER)
+ HandleQueue(GC_QUEUE_FILTER)
+ queue = GC_QUEUE_FILTER+1
if (GC_QUEUE_CHECK)
HandleQueue(GC_QUEUE_CHECK)
queue = GC_QUEUE_CHECK+1
@@ -103,8 +134,21 @@ SUBSYSTEM_DEF(garbage)
state = SS_RUNNING
break
-/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK)
- if (level == GC_QUEUE_CHECK)
+
+
+/datum/controller/subsystem/garbage/proc/InitQueues()
+ if (isnull(queues)) // Only init the queues if they don't already exist, prevents overriding of recovered lists
+ queues = new(GC_QUEUE_COUNT)
+ pass_counts = new(GC_QUEUE_COUNT)
+ fail_counts = new(GC_QUEUE_COUNT)
+ for(var/i in 1 to GC_QUEUE_COUNT)
+ queues[i] = list()
+ pass_counts[i] = 0
+ fail_counts[i] = 0
+
+
+/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_FILTER)
+ if (level == GC_QUEUE_FILTER)
delslasttick = 0
gcedlasttick = 0
var/cut_off_time = world.time - collection_timeout[level] //ignore entries newer then this
@@ -119,30 +163,33 @@ SUBSYSTEM_DEF(garbage)
lastlevel = level
- //We do this rather then for(var/refID in queue) because that sort of for loop copies the whole list.
+// 1 from the hard reference in the queue, and 1 from the variable used before this
+#define REFS_WE_EXPECT 2
+
+ //We do this rather then for(var/list/ref_info in queue) because that sort of for loop copies the whole list.
//Normally this isn't expensive, but the gc queue can grow to 40k items, and that gets costly/causes overrun.
for (var/i in 1 to length(queue))
var/list/L = queue[i]
- if (length(L) < 2)
+ if (length(L) < GC_QUEUE_ITEM_INDEX_COUNT)
count++
if (MC_TICK_CHECK)
return
continue
- var/GCd_at_time = L[1]
- if(GCd_at_time > cut_off_time)
+ var/queued_at_time = L[GC_QUEUE_ITEM_QUEUE_TIME]
+ if(queued_at_time > cut_off_time)
break // Everything else is newer, skip them
count++
- var/refID = L[2]
- var/datum/D
- D = locate(refID)
- if (!D || D.gc_destroyed != GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
+ var/datum/D = L[GC_QUEUE_ITEM_REF]
+
+ // If that's all we've got, send er off
+ if (refcount(D) == REFS_WE_EXPECT)
++gcedlasttick
++totalgcs
pass_counts[level]++
#ifdef REFERENCE_TRACKING
- reference_find_on_fail -= refID //It's deleted we don't care anymore.
+ reference_find_on_fail -= ref(D) //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
return
@@ -158,22 +205,33 @@ SUBSYSTEM_DEF(garbage)
switch (level)
if (GC_QUEUE_CHECK)
#ifdef REFERENCE_TRACKING
- if(reference_find_on_fail[refID])
- INVOKE_ASYNC(D, /datum/proc/find_references)
+ // Decides how many refs to look for (potentially)
+ // Based off the remaining and the ones we can account for
+ var/remaining_refs = refcount(D) - REFS_WE_EXPECT
+ if(reference_find_on_fail[ref(D)])
+ INVOKE_ASYNC(D, TYPE_PROC_REF(/datum,find_references), remaining_refs)
ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
else
- INVOKE_ASYNC(D, /datum/proc/find_references)
+ INVOKE_ASYNC(D, TYPE_PROC_REF(/datum,find_references), remaining_refs)
ref_searching = TRUE
#endif
- reference_find_on_fail -= refID
+ reference_find_on_fail -= ref(D)
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
- log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
+ var/message = "## TESTING: GC: -- [ref(D)] | [type] was unable to be GC'd --"
+ message = "[message] (ref count of [refcount(D)])"
+ log_world(message)
+
+ /*var/detail = D.dump_harddel_info()
+ if(detail)
+ LAZYADD(I.extra_details, detail)*/
+
#ifdef TESTING
- for(var/client/admin as anything in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
+ for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
+ var/client/admin = c
if(!check_rights_for(admin, R_ADMIN))
continue
to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --")
@@ -205,36 +263,41 @@ SUBSYSTEM_DEF(garbage)
queue.Cut(1,count+1)
count = 0
-/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
+#undef REFS_WE_EXPECT
+
+/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_FILTER)
if (isnull(D))
return
if (level > GC_QUEUE_COUNT)
HardDelete(D)
return
- var/gctime = world.time
- var/refid = "\ref[D]"
+ var/queue_time = world.time
- D.gc_destroyed = gctime
- var/list/queue = queues[level]
+ if (D.gc_destroyed <= 0)
+ D.gc_destroyed = queue_time
- queue[++queue.len] = list(gctime, refid) // not += for byond reasons
+ var/list/queue = queues[level]
+ queue[++queue.len] = list(queue_time, D, D.gc_destroyed) // not += for byond reasons
//this is mainly to separate things profile wise.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
++delslasttick
++totaldels
var/type = D.type
- var/refID = "\ref[D]"
+ var/refID = ref(D)
+ var/datum/qdel_item/type_info = items[type]
+ /*var/detail = D.dump_harddel_info()
+ if(detail)
+ LAZYADD(type_info.extra_details, detail)*/
var/tick_usage = TICK_USAGE
del(D)
tick_usage = TICK_USAGE_TO_MS(tick_usage)
- var/datum/qdel_item/I = items[type]
- I.hard_deletes++
- I.hard_delete_time += tick_usage
- if (tick_usage > I.hard_delete_max)
- I.hard_delete_max = tick_usage
+ type_info.hard_deletes++
+ type_info.hard_delete_time += tick_usage
+ if (tick_usage > type_info.hard_delete_max)
+ type_info.hard_delete_max = tick_usage
if (tick_usage > highest_del_ms)
highest_del_ms = tick_usage
highest_del_type_string = "[type]"
@@ -245,15 +308,17 @@ SUBSYSTEM_DEF(garbage)
postpone(time)
var/threshold = 0.5 // Default, make a config
if (threshold && (time > threshold SECONDS))
- if (!(I.qdel_flags & QDEL_ITEM_ADMINS_WARNED))
- log_and_message_admins("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete)")
- I.qdel_flags |= QDEL_ITEM_ADMINS_WARNED
- I.hard_deletes_over_threshold++
+ if (!(type_info.qdel_flags & QDEL_ITEM_ADMINS_WARNED))
+ log_game("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete)")
+ message_admins("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete).")
+ type_info.qdel_flags |= QDEL_ITEM_ADMINS_WARNED
+ type_info.hard_deletes_over_threshold++
var/overrun_limit = 0 // Default, make a config
- if (overrun_limit && I.hard_deletes_over_threshold >= overrun_limit)
- I.qdel_flags |= QDEL_ITEM_SUSPENDED_FOR_LAG
+ if (overrun_limit && type_info.hard_deletes_over_threshold >= overrun_limit)
+ type_info.qdel_flags |= QDEL_ITEM_SUSPENDED_FOR_LAG
/datum/controller/subsystem/garbage/Recover()
+ InitQueues() //We first need to create the queues before recovering data
if (istype(SSgarbage.queues))
for (var/i in 1 to SSgarbage.queues.len)
queues[i] |= SSgarbage.queues[i]
@@ -272,79 +337,85 @@ SUBSYSTEM_DEF(garbage)
var/no_hint = 0 //!Number of times it's not even bother to give a qdel hint
var/slept_destroy = 0 //!Number of times it's slept in its destroy
var/qdel_flags = 0 //!Flags related to this type's trip thru qdel.
+ var/list/extra_details //!Lazylist of string metadata about the deleted objects
/datum/qdel_item/New(mytype)
name = "[mytype]"
-
/// Should be treated as a replacement for the 'del' keyword.
///
/// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
-/proc/qdel(datum/D, force=FALSE, ...)
- if(!istype(D))
- del(D)
+/proc/qdel(datum/to_delete, force = FALSE)
+ if(!istype(to_delete))
+ del(to_delete)
return
- var/datum/qdel_item/I = SSgarbage.items[D.type]
- if (!I)
- I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type)
- I.qdels++
+ var/datum/qdel_item/trash = SSgarbage.items[to_delete.type]
+ if (isnull(trash))
+ trash = SSgarbage.items[to_delete.type] = new /datum/qdel_item(to_delete.type)
+ trash.qdels++
- if(isnull(D.gc_destroyed))
- if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
- return
- D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
- var/start_time = world.time
- var/start_tick = world.tick_usage
- SEND_SIGNAL(D, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
- var/hint = D.Destroy(arglist(args.Copy(2))) // Let our friend know they're about to get fucked up.
- if(world.time != start_time)
- I.slept_destroy++
- else
- I.destroy_time += TICK_USAGE_TO_MS(start_tick)
- if(!D)
+ if(!isnull(to_delete.gc_destroyed))
+ if(to_delete.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
+ CRASH("[to_delete.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
+ return
+
+ if (SEND_SIGNAL(to_delete, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
+ return
+
+ to_delete.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
+ var/start_time = world.time
+ var/start_tick = world.tick_usage
+ SEND_SIGNAL(to_delete, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
+ var/hint = to_delete.Destroy(force) // Let our friend know they're about to get fucked up.
+
+ if(world.time != start_time)
+ trash.slept_destroy++
+ else
+ trash.destroy_time += TICK_USAGE_TO_MS(start_tick)
+
+ if(isnull(to_delete))
+ return
+
+ switch(hint)
+ if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
+ SSgarbage.Queue(to_delete)
+ if (QDEL_HINT_IWILLGC)
+ to_delete.gc_destroyed = world.time
return
- switch(hint)
- if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
- SSgarbage.Queue(D)
- if (QDEL_HINT_IWILLGC)
- D.gc_destroyed = world.time
+ if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
+ if(!force)
+ to_delete.gc_destroyed = null //clear the gc variable (important!)
return
- if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
- if(!force)
- D.gc_destroyed = null //clear the gc variable (important!)
- return
- // Returning LETMELIVE after being told to force destroy
- // indicates the objects Destroy() does not respect force
- #ifdef TESTING
- if(!I.no_respect_force)
- testing("WARNING: [D.type] has been force deleted, but is \
- returning an immortal QDEL_HINT, indicating it does \
- not respect the force flag for qdel(). It has been \
- placed in the queue, further instances of this type \
- will also be queued.")
- #endif
- I.no_respect_force++
+ // Returning LETMELIVE after being told to force destroy
+ // indicates the objects Destroy() does not respect force
+ #ifdef TESTING
+ if(!trash.no_respect_force)
+ testing("WARNING: [to_delete.type] has been force deleted, but is \
+ returning an immortal QDEL_HINT, indicating it does \
+ not respect the force flag for qdel(). It has been \
+ placed in the queue, further instances of this type \
+ will also be queued.")
+ #endif
+ trash.no_respect_force++
- SSgarbage.Queue(D)
- if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
- SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
- if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
- SSgarbage.HardDelete(D)
- #ifdef REFERENCE_TRACKING
- if (QDEL_HINT_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
- SSgarbage.Queue(D)
- D.find_references()
- if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object.
- SSgarbage.Queue(D)
- SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
+ SSgarbage.Queue(to_delete)
+ if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
+ SSgarbage.Queue(to_delete, GC_QUEUE_HARDDELETE)
+ if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
+ SSgarbage.HardDelete(to_delete)
+ #ifdef REFERENCE_TRACKING
+ if (QDEL_HINT_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
+ SSgarbage.Queue(to_delete)
+ INVOKE_ASYNC(to_delete, TYPE_PROC_REF(/datum, find_references))
+ if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object.
+ SSgarbage.Queue(to_delete)
+ SSgarbage.reference_find_on_fail[ref(to_delete)] = TRUE
+ #endif
+ else
+ #ifdef TESTING
+ if(!trash.no_hint)
+ testing("WARNING: [to_delete.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
#endif
- else
- #ifdef TESTING
- if(!I.no_hint)
- testing("WARNING: [D.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
- #endif
- I.no_hint++
- SSgarbage.Queue(D)
- else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
- CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
+ trash.no_hint++
+ SSgarbage.Queue(to_delete)
diff --git a/code/controllers/subsystems/overlays.dm b/code/controllers/subsystems/overlays.dm
index 25dbfbd62a6..9d840933cb7 100644
--- a/code/controllers/subsystems/overlays.dm
+++ b/code/controllers/subsystems/overlays.dm
@@ -77,23 +77,31 @@ SUBSYSTEM_DEF(overlays)
var/list/result = list()
var/icon/icon = subject.icon
for (var/atom/entry as anything in sources)
- if (!entry)
- continue
- else if (istext(entry))
- result += GetStateAppearance(icon, entry)
- else if (isicon(entry))
- result += GetIconAppearance(entry)
- else
- if (isloc(entry))
- if (entry.flags & OVERLAY_QUEUED)
- entry.ImmediateOverlayUpdate()
- if (!ispath(entry))
- result += entry.appearance
- else
- var/image/image = entry
- result += image.appearance
+ AppearanceListEntry(entry, result, icon)
return result
+//Fixes runtime with overlays present in 515
+/datum/controller/subsystem/overlays/proc/AppearanceListEntry(var/atom/entry,var/list/result,var/icon/icon)
+ if (!entry)
+ return
+ else if(islist(entry))
+ var/list/entry_list = entry
+ for(var/entry_item in entry_list)
+ AppearanceListEntry(entry_item)
+ else if (istext(entry))
+ result += GetStateAppearance(icon, entry)
+ else if (isicon(entry))
+ result += GetIconAppearance(entry)
+ else
+ if (isloc(entry))
+ if (entry.flags & OVERLAY_QUEUED)
+ entry.ImmediateOverlayUpdate()
+ if (!ispath(entry))
+ if(entry.appearance)
+ result += entry.appearance
+ else
+ var/image/image = entry
+ result += image.appearance
/// Enqueues the atom for an overlay update if not already queued
/atom/proc/QueueOverlayUpdate()
diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm
index d5540bd7903..3a95ba1db59 100644
--- a/code/datums/chat_message.dm
+++ b/code/datums/chat_message.dm
@@ -147,14 +147,14 @@ var/list/runechat_image_cache = list()
// Append prefixes
if(extra_classes.Find("virtual-speaker"))
- LAZYADD(prefixes, "\icon[runechat_image_cache["radio"]]")
+ LAZYADD(prefixes, "[icon2html(runechat_image_cache["radio"],owner.client)]")
if(extra_classes.Find("emote"))
// Icon on both ends?
//var/image/I = runechat_image_cache["emote"]
- //text = "\icon[I][text]\icon[I]"
+ //text = "icon2html(I)[text]icon2html(I)"
// Icon on one end?
- //LAZYADD(prefixes, "\icon[runechat_image_cache["emote"]]")
+ //LAZYADD(prefixes, "icon2html(runechat_image_cache["emote")]")
// Asterisks instead?
text = "* [text] *"
diff --git a/code/datums/components/recursive_move.dm b/code/datums/components/recursive_move.dm
new file mode 100644
index 00000000000..cf26942495b
--- /dev/null
+++ b/code/datums/components/recursive_move.dm
@@ -0,0 +1,119 @@
+/**
+ * Recursive move listener
+ * Can be added willy-nilly to anything where the COMSIG_OBSERVER_MOVE signal should also trigger on a parent moving.
+ * Previously there was a system where COMSIG_OBSERVER_MOVE was always recursively propogated, but that was unnecessary bloat.
+ */
+/datum/component/recursive_move
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS //This makes it so pretty much nothing happens when a duplicate component is created since we don't actually override InheritComponent
+ var/atom/movable/holder
+ var/list/parents = list()
+ var/noparents = FALSE
+
+/datum/component/recursive_move/RegisterWithParent()
+ . = ..()
+ holder = parent
+ RegisterSignal(holder, COMSIG_PARENT_QDELETING, PROC_REF(on_holder_qdel))
+ spawn(0) // Delayed action if our holder is spawned in nullspace and then loc = target, hopefully this catches it. VV Add item does this, for example.
+ if(!QDELETED(src))
+ setup_parents()
+
+/datum/component/recursive_move/proc/setup_parents()
+ if(length(parents)) // safety check just incase this was called without clearing
+ reset_parents()
+ var/atom/movable/cur_parent = holder?.loc // first loc could be null
+ var/recursion = 0 // safety check - max iterations
+ while(istype(cur_parent) && (recursion < 64))
+ if(cur_parent == cur_parent.loc) //safety check incase a thing is somehow inside itself, cancel
+ log_debug("RECURSIVE_MOVE: Parent is inside itself. ([holder]) ([holder.type])")
+ reset_parents()
+ break
+ if(cur_parent in parents) //safety check incase of circular contents. (A inside B, B inside C, C inside A), cancel
+ log_debug("RECURSIVE_MOVE: Parent is inside a circular inventory. ([holder]) ([holder.type])")
+ reset_parents()
+ break
+ recursion++
+ parents += cur_parent
+ RegisterSignal(cur_parent, COMSIG_ATOM_EXITED, PROC_REF(heirarchy_changed))
+ RegisterSignal(cur_parent, COMSIG_PARENT_QDELETING, PROC_REF(on_qdel))
+
+ cur_parent = cur_parent.loc
+
+ if(recursion >= 64) // If we escaped due to iteration limit, cancel
+ log_debug("RECURSIVE_MOVE: Parent hit recursion limit. ([holder]) ([holder.type])")
+ reset_parents()
+ parents.Cut()
+
+ if(length(parents))
+ //Only need to watch top parent for movement. Everything is covered by Exited
+ RegisterSignal(parents[parents.len], COMSIG_ATOM_ENTERING, PROC_REF(top_moved))
+
+ //If we have no parents of type atom/movable then we wait to see if that changes, checking every time our holder moves.
+ if(!length(parents) && !noparents)
+ noparents = TRUE
+ RegisterSignal(holder, COMSIG_ATOM_ENTERING, PROC_REF(setup_parents))
+
+ if(length(parents) && noparents)
+ noparents = FALSE
+ UnregisterSignal(holder, COMSIG_ATOM_ENTERING)
+
+
+/datum/component/recursive_move/proc/unregister_signals()
+ if(noparents) // safety check
+ noparents = FALSE
+ UnregisterSignal(holder, COMSIG_ATOM_ENTERING)
+ if(!length(parents))
+ return
+ for(var/atom/movable/cur_parent in parents)
+ UnregisterSignal(cur_parent, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(cur_parent, COMSIG_ATOM_EXITED)
+
+ UnregisterSignal(parents[parents.len], COMSIG_ATOM_ENTERING)
+
+//Parent at top of heirarchy moved.
+/datum/component/recursive_move/proc/top_moved(var/atom/movable/am, var/atom/new_loc, var/atom/old_loc)
+ SIGNAL_HANDLER
+ SEND_SIGNAL(holder, COMSIG_OBSERVER_MOVED, old_loc, new_loc)
+
+//One of the parents other than the top parent moved.
+/datum/component/recursive_move/proc/heirarchy_changed(var/atom/old_loc, var/atom/movable/am, var/atom/new_loc)
+ SIGNAL_HANDLER
+ SEND_SIGNAL(holder, COMSIG_OBSERVER_MOVED, old_loc, new_loc)
+ //Rebuild our list of parents
+ reset_parents()
+ setup_parents()
+
+//Some things will move their contents on qdel so we should prepare ourselves to be moved.
+//If this qdel does destroy our holder, on_holder_qdel will handle preperations for GC
+/datum/component/recursive_move/proc/on_qdel()
+ reset_parents()
+ noparents = TRUE
+ RegisterSignal(holder, COMSIG_ATOM_ENTERING, PROC_REF(setup_parents))
+
+/datum/component/recursive_move/proc/on_holder_qdel()
+ UnregisterSignal(holder, COMSIG_PARENT_QDELETING)
+ reset_parents()
+ holder = null
+ qdel(src)
+
+/datum/component/recursive_move/Destroy()
+ . = ..()
+ reset_parents()
+ if(holder) UnregisterSignal(holder, COMSIG_PARENT_QDELETING)
+ holder = null
+
+/datum/component/recursive_move/proc/reset_parents()
+ unregister_signals()
+ parents.Cut()
+
+//the banana peel of testing stays
+/obj/item/weapon/bananapeel/testing
+ name = "banana peel of testing"
+ desc = "spams world log with debugging information"
+
+/obj/item/weapon/bananapeel/testing/proc/shmove(var/atom/source, var/atom/old_loc, var/atom/new_loc)
+ world.log << "the [source] moved from [old_loc]([old_loc.x],[old_loc.y],[old_loc.z]) to [new_loc]([new_loc.x],[new_loc.y],[new_loc.z])"
+
+/obj/item/weapon/bananapeel/testing/Initialize()
+ . = ..()
+ AddComponent(/datum/component/recursive_move)
+ RegisterSignal(src, COMSIG_OBSERVER_MOVED, PROC_REF(shmove))
diff --git a/code/datums/observation/_debug.dm b/code/datums/observation/_debug.dm
deleted file mode 100644
index 5b805aa9302..00000000000
--- a/code/datums/observation/_debug.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/****************
-* Debug Support *
-****************/
-
-/datum/all_observable_events
- var/list/events
-
-/datum/all_observable_events/New()
- events = list()
- ..()
diff --git a/code/datums/observation/_defines.dm b/code/datums/observation/_defines.dm
deleted file mode 100644
index 902d6558709..00000000000
--- a/code/datums/observation/_defines.dm
+++ /dev/null
@@ -1 +0,0 @@
-#define CANCEL_MOVE_EVENT -55
diff --git a/code/datums/observation/destroyed.dm b/code/datums/observation/destroyed.dm
index 650909f86d8..9846eda6daf 100644
--- a/code/datums/observation/destroyed.dm
+++ b/code/datums/observation/destroyed.dm
@@ -5,11 +5,12 @@
//
// Arguments that the called proc should expect:
// /datum/destroyed_instance: The instance that was destroyed.
-
+/*
/decl/observ/destroyed
name = "Destroyed"
+*/
+//Deprecated in favor of Comsigs
/datum/Destroy()
- if(GLOB.destroyed_event)
- GLOB.destroyed_event.raise_event(src)
+ SEND_SIGNAL(src,COMSIG_OBSERVER_DESTROYED)
. = ..()
diff --git a/code/datums/observation/dir_set.dm b/code/datums/observation/dir_set.dm
deleted file mode 100644
index a626a07c4b3..00000000000
--- a/code/datums/observation/dir_set.dm
+++ /dev/null
@@ -1,35 +0,0 @@
-// Observer Pattern Implementation: Direction Set
-// Registration type: /atom
-//
-// Raised when: An /atom changes dir using the set_dir() proc.
-//
-// Arguments that the called proc should expect:
-// /atom/dir_changer: The instance that changed direction
-// /old_dir: The dir before the change.
-// /new_dir: The dir after the change.
-
-GLOBAL_DATUM_INIT(dir_set_event, /decl/observ/dir_set, new)
-
-/decl/observ/dir_set
- name = "Direction Set"
- expected_type = /atom
-
-/decl/observ/dir_set/register(var/atom/dir_changer, var/datum/listener, var/proc_call)
- . = ..()
-
- // Listen to the parent if possible.
- if(. && istype(dir_changer.loc, /atom/movable)) // We don't care about registering to turfs.
- register(dir_changer.loc, dir_changer, /atom/proc/recursive_dir_set)
-
-/*********************
-* Direction Handling *
-*********************/
-
-/atom/movable/Entered(var/atom/movable/am, atom/old_loc)
- . = ..()
- if(. != CANCEL_MOVE_EVENT && GLOB.dir_set_event.has_listeners(am))
- GLOB.dir_set_event.register(src, am, /atom/proc/recursive_dir_set)
-
-/atom/movable/Exited(var/atom/movable/am, atom/old_loc)
- . = ..()
- GLOB.dir_set_event.unregister(src, am, /atom/proc/recursive_dir_set)
diff --git a/code/datums/observation/equipped.dm b/code/datums/observation/equipped.dm
index 4142050a356..a64d1e76df4 100644
--- a/code/datums/observation/equipped.dm
+++ b/code/datums/observation/equipped.dm
@@ -7,6 +7,7 @@
// /mob/equipper: The mob that equipped the item.
// /obj/item/item: The equipped item.
// slot: The slot equipped to.
+/*
GLOBAL_DATUM_INIT(mob_equipped_event, /decl/observ/mob_equipped, new)
/decl/observ/mob_equipped
@@ -27,12 +28,13 @@ GLOBAL_DATUM_INIT(item_equipped_event, /decl/observ/item_equipped, new)
/decl/observ/item_equipped
name = "Item Equipped"
expected_type = /obj/item
-
+*/
+//Deprecated in favor of comsigs
/********************
* Equipped Handling *
********************/
/obj/item/equipped(var/mob/user, var/slot)
. = ..()
- GLOB.mob_equipped_event.raise_event(user, src, slot)
- GLOB.item_equipped_event.raise_event(src, user, slot)
+ SEND_SIGNAL(user, COMSIG_OBSERVER_MOB_EQUIPPED, src, slot)
+ SEND_SIGNAL(src, COMSIG_OBSERVER_ITEM_EQUIPPED, user, slot)
diff --git a/code/datums/observation/helpers.dm b/code/datums/observation/helpers.dm
index 12feeba16df..bc1091c6687 100644
--- a/code/datums/observation/helpers.dm
+++ b/code/datums/observation/helpers.dm
@@ -1,6 +1,7 @@
+/*
/atom/movable/proc/recursive_move(var/atom/movable/am, var/old_loc, var/new_loc)
- GLOB.moved_event.raise_event(src, old_loc, new_loc)
-
+ SEND_SIGNAL(src,COMSIG_OBSERVER_MOVED, old_loc, new_loc)
+*/
/atom/movable/proc/move_to_destination(var/atom/movable/am, var/old_loc, var/new_loc)
var/turf/T = get_turf(new_loc)
if(T && T != loc)
@@ -12,10 +13,12 @@
/datum/proc/qdel_self()
qdel(src)
-/proc/register_all_movement(var/event_source, var/listener)
- GLOB.moved_event.register(event_source, listener, /atom/movable/proc/recursive_move)
- GLOB.dir_set_event.register(event_source, listener, /atom/proc/recursive_dir_set)
+/*
+/proc/register_all_movement(var/event_source, var/datum/listener)
+ listener.RegisterSignal(event_source,COMSIG_OBSERVER_MOVED, /atom/movable/proc/recursive_move)
+ //GLOB.dir_set_event.register(event_source, listener, /atom/proc/recursive_dir_set)
-/proc/unregister_all_movement(var/event_source, var/listener)
- GLOB.moved_event.unregister(event_source, listener, /atom/movable/proc/recursive_move)
- GLOB.dir_set_event.unregister(event_source, listener, /atom/proc/recursive_dir_set)
+/proc/unregister_all_movement(var/event_source, var/datum/listener)
+ listener.UnregisterSignal(event_source,COMSIG_OBSERVER_MOVED)
+ //GLOB.dir_set_event.unregister(event_source, listener, /atom/proc/recursive_dir_set)
+*/
diff --git a/code/datums/observation/logged_in.dm b/code/datums/observation/logged_in.dm
deleted file mode 100644
index c59e146a485..00000000000
--- a/code/datums/observation/logged_in.dm
+++ /dev/null
@@ -1,21 +0,0 @@
-// Observer Pattern Implementation: Logged in
-// Registration type: /mob
-//
-// Raised when: A mob logs in (not client)
-//
-// Arguments that the called proc should expect:
-// /mob/joiner: The mob that has logged in
-
-GLOBAL_DATUM_INIT(logged_in_event, /decl/observ/logged_in, new)
-
-/decl/observ/logged_in
- name = "Logged In"
- expected_type = /mob
-
-/*****************
-* Login Handling *
-*****************/
-
-/mob/Login()
- ..()
- GLOB.logged_in_event.raise_event(src)
diff --git a/code/datums/observation/moved.dm b/code/datums/observation/moved.dm
index 3cd61c1cc6f..f6ba4668369 100644
--- a/code/datums/observation/moved.dm
+++ b/code/datums/observation/moved.dm
@@ -8,9 +8,10 @@
// /atom/old_loc: The loc before the move.
// /atom/new_loc: The loc after the move.
-
+/*
GLOBAL_DATUM_INIT(moved_event, /decl/observ/moved, new)
+
/decl/observ/moved
name = "Moved"
expected_type = /atom/movable
@@ -21,28 +22,30 @@ GLOBAL_DATUM_INIT(moved_event, /decl/observ/moved, new)
// Listen to the parent if possible.
if(. && istype(mover.loc, expected_type))
register(mover.loc, mover, /atom/movable/proc/recursive_move)
+*/
+//Deprecated in favor of comsigs
/********************
* Movement Handling *
********************/
+/*
/atom/movable/Entered(var/atom/movable/am, atom/old_loc)
. = ..()
- if(GLOB.moved_event.has_listeners(am))
- GLOB.moved_event.register(src, am, /atom/movable/proc/recursive_move)
+ am.RegisterSignal(src,COMSIG_OBSERVER_MOVED, /atom/movable/proc/recursive_move, override = TRUE)
/atom/movable/Exited(var/atom/movable/am, atom/old_loc)
. = ..()
- GLOB.moved_event.unregister(src, am, /atom/movable/proc/recursive_move)
-
+ am.UnregisterSignal(src,COMSIG_OBSERVER_MOVED)
+*/
// Entered() typically lifts the moved event, but in the case of null-space we'll have to handle it.
/atom/movable/Move()
var/old_loc = loc
. = ..()
if(. && !loc)
- GLOB.moved_event.raise_event(src, old_loc, null)
+ SEND_SIGNAL(src,COMSIG_OBSERVER_MOVED, old_loc, null)
/atom/movable/forceMove(atom/destination)
var/old_loc = loc
. = ..()
if(. && !loc)
- GLOB.moved_event.raise_event(src, old_loc, null)
+ SEND_SIGNAL(src,COMSIG_OBSERVER_MOVED, old_loc, null)
diff --git a/code/datums/observation/observation.dm b/code/datums/observation/observation.dm
deleted file mode 100644
index db1f9e0d6ea..00000000000
--- a/code/datums/observation/observation.dm
+++ /dev/null
@@ -1,238 +0,0 @@
-//
-// Observer Pattern Implementation
-//
-// Implements a basic observer pattern with the following main procs:
-//
-// /decl/observ/proc/is_listening(var/event_source, var/datum/listener, var/proc_call)
-// event_source: The instance which is generating events.
-// listener: The instance which may be listening to events by event_source
-// proc_call: Optional. The specific proc to call when the event is raised.
-//
-// Returns true if listener is listening for events by event_source, and proc_call supplied is either null or one of the proc that will be called when an event is raised.
-//
-// /decl/observ/proc/has_listeners(var/event_source)
-// event_source: The instance which is generating events.
-//
-// Returns true if the given event_source has any listeners at all, globally or to specific event sources.
-//
-// /decl/observ/proc/register(var/event_source, var/datum/listener, var/proc_call)
-// event_source: The instance you wish to receive events from.
-// listener: The instance/owner of the proc to call when an event is raised by the event_source.
-// proc_call: The proc to call when an event is raised.
-//
-// It is possible to register the same listener to the same event_source multiple times as long as it is using different proc_calls.
-// Registering again using the same event_source, listener, and proc_call that has been registered previously will have no additional effect.
-// I.e.: The proc_call will still only be called once per raised event. That particular proc_call will only have to be unregistered once.
-//
-// When proc_call is called the first argument is always the source of the event (event_source).
-// Additional arguments may or may not be supplied, see individual event definition files (destroyed.dm, moved.dm, etc.) for details.
-//
-// The instance making the register() call is also responsible for calling unregister(), see below for additonal details, including when event_source is destroyed.
-// This can be handled by listening to the event_source's destroyed event, unregistering in the listener's Destroy() proc, etc.
-//
-// /decl/observ/proc/unregister(var/event_source, var/datum/listener, var/proc_call)
-// event_source: The instance you wish to stop receiving events from.
-// listener: The instance which will no longer receive the events.
-// proc_call: Optional: The proc_call to unregister.
-//
-// Unregisters the listener from the event_source.
-// If a proc_call has been supplied only that particular proc_call will be unregistered. If the proc_call isn't currently registered there will be no effect.
-// If no proc_call has been supplied, the listener will have all registrations made to the given event_source undone.
-//
-// /decl/observ/proc/register_global(var/datum/listener, var/proc_call)
-// listener: The instance/owner of the proc to call when an event is raised by any and all sources.
-// proc_call: The proc to call when an event is raised.
-//
-// Works very much the same as register(), only the listener/proc_call will receive all relevant events from all event sources.
-// Global registrations can overlap with registrations made to specific event sources and these will not affect each other.
-//
-// /decl/observ/proc/unregister_global(var/datum/listener, var/proc_call)
-// listener: The instance/owner of the proc which will no longer receive the events.
-// proc_call: Optional: The proc_call to unregister.
-//
-// Works very much the same as unregister(), only it undoes global registrations instead.
-//
-// /decl/observ/proc/raise_event(src, ...)
-// Should never be called unless implementing a new event type.
-// The first argument shall always be the event_source belonging to the event. Beyond that there are no restrictions.
-
-/decl/observ
- var/name = "Unnamed Event" // The name of this event, used mainly for debug/VV purposes. The list of event managers can be reached through the "Debug Controller" verb, selecting the "Observation" entry.
- var/expected_type = /datum // The expected event source for this event. register() will CRASH() if it receives an unexpected type.
- var/list/event_sources = list() // Associative list of event sources, each with their own associative list. This associative list contains an instance/list of procs to call when the event is raised.
- var/list/global_listeners = list() // Associative list of instances that listen to all events of this type (as opposed to events belonging to a specific source) and the proc to call.
-
-/decl/observ/New()
- GLOB.all_observable_events.events += src
- . = ..()
-
-/decl/observ/proc/is_listening(var/event_source, var/datum/listener, var/proc_call)
- // Return whether there are global listeners unless the event source is given.
- if (!event_source)
- return !!global_listeners.len
-
- // Return whether anything is listening to a source, if no listener is given.
- if (!listener)
- return global_listeners.len || (event_source in event_sources)
-
- // Return false if nothing is associated with that source.
- if (!(event_source in event_sources))
- return FALSE
-
- // Get and check the listeners for the reuqested event.
- var/listeners = event_sources[event_source]
- if (!(listener in listeners))
- return FALSE
-
- // Return true unless a specific callback needs checked.
- if (!proc_call)
- return TRUE
-
- // Check if the specific callback exists.
- var/list/callback = listeners[listener]
- if (!callback)
- return FALSE
-
- return (proc_call in callback)
-
-/decl/observ/proc/has_listeners(var/event_source)
- return is_listening(event_source)
-
-/decl/observ/proc/register(var/datum/event_source, var/datum/listener, var/proc_call)
- // Sanity checking.
- if (!(event_source && listener && proc_call))
- return FALSE
- if (istype(event_source, /decl/observ))
- return FALSE
-
- // Crash if the event source is the wrong type.
- if (!istype(event_source, expected_type))
- CRASH("Unexpected type. Expected [expected_type], was [event_source.type]")
-
- // Setup the listeners for this source if needed.
- var/list/listeners = event_sources[event_source]
- if (!listeners)
- listeners = list()
- event_sources[event_source] = listeners
-
- // Make sure the callbacks are a list.
- var/list/callbacks = listeners[listener]
- if (!callbacks)
- callbacks = list()
- listeners[listener] = callbacks
-
- // If the proc_call is already registered skip
- if(proc_call in callbacks)
- return FALSE
-
- // Add the callback, and return true.
- callbacks += proc_call
- return TRUE
-
-/decl/observ/proc/unregister(var/event_source, var/datum/listener, var/proc_call)
- // Sanity.
- if (!(event_source && listener && (event_source in event_sources)))
- return FALSE
-
- // Return false if nothing is listening for this event.
- var/list/listeners = event_sources[event_source]
- if (!listeners)
- return FALSE
-
- // Remove all callbacks if no specific one is given.
- if (!proc_call)
- if(listeners.Remove(listener))
- // Perform some cleanup and return true.
- if (!listeners.len)
- event_sources -= event_source
- return TRUE
- return FALSE
-
- // See if the listener is registered.
- var/list/callbacks = listeners[listener]
- if (!callbacks)
- return FALSE
-
- // See if the callback exists.
- if(!callbacks.Remove(proc_call))
- return FALSE
-
- if (!callbacks.len)
- listeners -= listener
- if (!listeners.len)
- event_sources -= event_source
- return TRUE
-
-/decl/observ/proc/register_global(var/datum/listener, var/proc_call)
- // Sanity.
- if (!(listener && proc_call))
- return FALSE
-
- // Make sure the callbacks are setup.
- var/list/callbacks = global_listeners[listener]
- if (!callbacks)
- callbacks = list()
- global_listeners[listener] = callbacks
-
- // Add the callback and return true.
- callbacks |= proc_call
- return TRUE
-
-/decl/observ/proc/unregister_global(var/datum/listener, var/proc_call)
- // Return false unless the listener is set as a global listener.
- if (!(listener && (listener in global_listeners)))
- return FALSE
-
- // Remove all callbacks if no specific one is given.
- if (!proc_call)
- global_listeners -= listener
- return TRUE
-
- // See if the listener is registered.
- var/list/callbacks = global_listeners[listener]
- if (!callbacks)
- return FALSE
-
- // See if the callback exists.
- if(!callbacks.Remove(proc_call))
- return FALSE
-
- if (!callbacks.len)
- global_listeners -= listener
- return TRUE
-
-/decl/observ/proc/raise_event()
- // Sanity
- if (!args.len)
- return FALSE
-
- // Call the global listeners.
- for (var/datum/listener in global_listeners)
- var/list/callbacks = global_listeners[listener]
- for (var/proc_call in callbacks)
-
- // If the callback crashes, record the error and remove it.
- try
- call(listener, proc_call)(arglist(args))
- catch (var/exception/e)
- error("[e.name] - [e.file] - [e.line]")
- error(e.desc)
- unregister_global(listener, proc_call)
-
- // Call the listeners for this specific event source, if they exist.
- var/source = args[1]
- if (source in event_sources)
- var/list/listeners = event_sources[source]
- for (var/datum/listener in listeners)
- var/list/callbacks = listeners[listener]
- for (var/proc_call in callbacks)
-
- // If the callback crashes, record the error and remove it.
- try
- call(listener, proc_call)(arglist(args))
- catch (var/exception/e)
- error("[e.name] - [e.file] - [e.line]")
- error(e.desc)
- unregister(source, listener, proc_call)
-
- return TRUE
diff --git a/code/datums/observation/power_change.dm b/code/datums/observation/power_change.dm
index 4581dab84ac..2a5ef3b5791 100644
--- a/code/datums/observation/power_change.dm
+++ b/code/datums/observation/power_change.dm
@@ -5,7 +5,7 @@
//
// Arguments that the called proc should expect:
// /area: The area experiencing the power change
-
+/*
GLOBAL_DATUM_INIT(apc_event, /decl/observ/area_power_change, new)
/decl/observ/area_power_change
@@ -15,7 +15,9 @@ GLOBAL_DATUM_INIT(apc_event, /decl/observ/area_power_change, new)
/********************
* Movement Handling *
********************/
+*/
+//Deprecated in favor of comsigs
/area/power_change()
. = ..()
- GLOB.apc_event.raise_event(src)
+ SEND_SIGNAL(src,COMSIG_OBSERVER_APC)
diff --git a/code/datums/observation/shuttle_added.dm b/code/datums/observation/shuttle_added.dm
index dfd95170a03..2dd865a5437 100644
--- a/code/datums/observation/shuttle_added.dm
+++ b/code/datums/observation/shuttle_added.dm
@@ -5,12 +5,14 @@
//
// Arguments that the called proc should expect:
// /datum/shuttle/shuttle: the new shuttle
-
+/*
GLOBAL_DATUM_INIT(shuttle_added, /decl/observ/shuttle_added, new)
/decl/observ/shuttle_added
name = "Shuttle Added"
expected_type = /datum/shuttle
+*/
+//Deprecated in favor of comsigs
/*****************************
* Shuttle Added Handling *
@@ -19,4 +21,4 @@ GLOBAL_DATUM_INIT(shuttle_added, /decl/observ/shuttle_added, new)
/datum/controller/subsystem/shuttles/initialize_shuttle()
. = ..()
if(.)
- GLOB.shuttle_added.raise_event(.)
\ No newline at end of file
+ SEND_SIGNAL(SSshuttles,COMSIG_OBSERVER_SHUTTLE_ADDED,.)
diff --git a/code/datums/observation/shuttle_moved.dm b/code/datums/observation/shuttle_moved.dm
deleted file mode 100644
index 35bff0d6b92..00000000000
--- a/code/datums/observation/shuttle_moved.dm
+++ /dev/null
@@ -1,38 +0,0 @@
-// Observer Pattern Implementation: Shuttle Moved
-// Registration type: /datum/shuttle/autodock
-//
-// Raised when: A shuttle has moved to a new landmark.
-//
-// Arguments that the called proc should expect:
-// /datum/shuttle/shuttle: the shuttle moving
-// /obj/effect/shuttle_landmark/old_location: the old location's shuttle landmark
-// /obj/effect/shuttle_landmark/new_location: the new location's shuttle landmark
-
-// Observer Pattern Implementation: Shuttle Pre Move
-// Registration type: /datum/shuttle/autodock
-//
-// Raised when: A shuttle is about to move to a new landmark.
-//
-// Arguments that the called proc should expect:
-// /datum/shuttle/shuttle: the shuttle moving
-// /obj/effect/shuttle_landmark/old_location: the old location's shuttle landmark
-// /obj/effect/shuttle_landmark/new_location: the new location's shuttle landmark
-
-GLOBAL_DATUM_INIT(shuttle_moved_event, /decl/observ/shuttle_moved, new)
-
-/decl/observ/shuttle_moved
- name = "Shuttle Moved"
- expected_type = /datum/shuttle
-
-GLOBAL_DATUM_INIT(shuttle_pre_move_event, /decl/observ/shuttle_pre_move, new)
-
-/decl/observ/shuttle_pre_move
- name = "Shuttle Pre Move"
- expected_type = /datum/shuttle
-
-/*****************
-* Shuttle Moved/Pre Move Handling *
-*****************/
-
-// Located in modules/shuttle/shuttle.dm
-// Proc: /datum/shuttle/proc/attempt_move()
\ No newline at end of file
diff --git a/code/datums/observation/stat_set.dm b/code/datums/observation/stat_set.dm
index 6bc6ea45f05..c818a505320 100644
--- a/code/datums/observation/stat_set.dm
+++ b/code/datums/observation/stat_set.dm
@@ -7,12 +7,14 @@
// /mob/living/stat_mob: The mob whose stat changed
// /old_stat: Status before the change.
// /new_stat: Status after the change.
-
-GLOBAL_DATUM_INIT(stat_set_event, /decl/observ/stat_set, new)
+/*
+stat_set_event, /decl/observ/stat_set, new)
/decl/observ/stat_set
name = "Stat Set"
expected_type = /mob/living
+*/
+//Deprecated in favor of Comsigs
/****************
* Stat Handling *
@@ -21,7 +23,7 @@ GLOBAL_DATUM_INIT(stat_set_event, /decl/observ/stat_set, new)
var/old_stat = stat
. = ..()
if(stat != old_stat)
- GLOB.stat_set_event.raise_event(src, old_stat, new_stat)
+ SEND_SIGNAL(src, COMSIG_MOB_STATCHANGE, old_stat, new_stat)
if(isbelly(src.loc))
var/obj/belly/ourbelly = src.loc
diff --git a/code/datums/observation/turf_changed.dm b/code/datums/observation/turf_changed.dm
deleted file mode 100644
index 0bb0ed91158..00000000000
--- a/code/datums/observation/turf_changed.dm
+++ /dev/null
@@ -1,28 +0,0 @@
-// Observer Pattern Implementation: Turf Changed
-// Registration type: /turf
-//
-// Raised when: A turf has been changed using the ChangeTurf proc.
-//
-// Arguments that the called proc should expect:
-// /turf/affected: The turf that has changed
-// /old_density: Density before the change
-// /new_density: Density after the change
-// /old_opacity: Opacity before the change
-// /new_opacity: Opacity after the change
-
-var/decl/observ/turf_changed/turf_changed_event = new()
-
-/decl/observ/turf_changed
- name = "Turf Changed"
- expected_type = /turf
-
-/************************
-* Turf Changed Handling *
-************************/
-
-/turf/ChangeTurf(var/turf/N, var/tell_universe, var/force_lighting_update, var/preserve_outdoors)
- var/old_density = density
- var/old_opacity = opacity
- . = ..(N, tell_universe, force_lighting_update, preserve_outdoors)
- if(.)
- turf_changed_event.raise_event(src, old_density, density, old_opacity, opacity)
\ No newline at end of file
diff --git a/code/datums/observation/turf_enterexit.dm b/code/datums/observation/turf_enterexit.dm
index 30cec0c39d2..3f591986f07 100644
--- a/code/datums/observation/turf_enterexit.dm
+++ b/code/datums/observation/turf_enterexit.dm
@@ -8,7 +8,7 @@
// /atom/movable/moving_instance: The instance that entered/exited
// /atom/old_loc / /atom/new_loc: The previous/new loc of the mover
-
+/*
GLOBAL_DATUM_INIT(turf_entered_event, /decl/observ/turf_entered, new)
GLOBAL_DATUM_INIT(turf_exited_event, /decl/observ/turf_exited, new)
@@ -20,14 +20,18 @@ GLOBAL_DATUM_INIT(turf_exited_event, /decl/observ/turf_exited, new)
name = "Turf Exited"
expected_type = /turf
+*/
+//Deprecated in favor of Comsigs
+
/********************
* Movement Handling *
********************/
+
/turf/Entered(var/atom/movable/am, var/atom/old_loc)
. = ..()
- GLOB.turf_entered_event.raise_event(src, am, old_loc)
+ SEND_SIGNAL(src, COMSIG_OBSERVER_TURF_ENTERED, am, old_loc)
/turf/Exited(var/atom/movable/am, var/atom/new_loc)
. = ..()
- GLOB.turf_exited_event.raise_event(src, am, new_loc)
\ No newline at end of file
+ SEND_SIGNAL(src, COMSIG_OBSERVER_TURF_EXITED, am, new_loc)
diff --git a/code/datums/observation/unequipped.dm b/code/datums/observation/unequipped.dm
index 6ad8d8eca03..353332de022 100644
--- a/code/datums/observation/unequipped.dm
+++ b/code/datums/observation/unequipped.dm
@@ -6,7 +6,7 @@
// Arguments that the called proc should expect:
// /mob/equipped: The mob that unequipped/dropped the item.
// /obj/item/item: The unequipped item.
-
+/*
GLOBAL_DATUM_INIT(mob_unequipped_event, /decl/observ/mob_unequipped, new)
/decl/observ/mob_unequipped
@@ -27,6 +27,8 @@ GLOBAL_DATUM_INIT(item_unequipped_event, /decl/observ/item_unequipped, new)
/decl/observ/item_unequipped
name = "Item Unequipped"
expected_type = /obj/item
+*/
+//Deprecated in favor of comsigs
/**********************
* Unequipped Handling *
@@ -34,5 +36,10 @@ GLOBAL_DATUM_INIT(item_unequipped_event, /decl/observ/item_unequipped, new)
/obj/item/dropped(var/mob/user)
..()
- GLOB.mob_unequipped_event.raise_event(user, src)
- GLOB.item_unequipped_event.raise_event(src, user)
+ //SEND_SIGNAL(user, COMSIG_OBSERVER_MOB_UNEQUIPPED, src)
+ //SEND_SIGNAL(src, COMSIG_OBSERVER_ITEM_UNEQUIPPED, user)
+ if(user) // Cannot always guarantee that user won't be null
+ SEND_SIGNAL(user, COMSIG_OBSERVER_MOB_UNEQUIPPED, src)
+ SEND_SIGNAL(src, COMSIG_OBSERVER_ITEM_UNEQUIPPED, user)
+ else
+ SEND_SIGNAL(src, COMSIG_OBSERVER_ITEM_UNEQUIPPED)
diff --git a/code/datums/observation/z_moved.dm b/code/datums/observation/z_moved.dm
deleted file mode 100644
index ba30df374c6..00000000000
--- a/code/datums/observation/z_moved.dm
+++ /dev/null
@@ -1,16 +0,0 @@
-// Observer Pattern Implementation: Z_Moved
-// Registration type: /atom/movable
-//
-// Raised when: An /atom/movable instance has changed z-levels by any means.
-//
-// Arguments that the called proc should expect:
-// /atom/movable/moving_instance: The instance that moved
-// old_z: The z number before the move.
-// new_z: The z number after the move.
-
-
-GLOBAL_DATUM_INIT(z_moved_event, /decl/observ/z_moved, new)
-
-/decl/observ/z_moved
- name = "Z_Moved"
- expected_type = /atom/movable
diff --git a/code/datums/observation/~cleanup.dm b/code/datums/observation/~cleanup.dm
deleted file mode 100644
index da902052bdd..00000000000
--- a/code/datums/observation/~cleanup.dm
+++ /dev/null
@@ -1,67 +0,0 @@
-GLOBAL_LIST_EMPTY(global_listen_count)
-GLOBAL_LIST_EMPTY(event_sources_count)
-GLOBAL_LIST_EMPTY(event_listen_count)
-
-/decl/observ/destroyed/raise_event()
- . = ..()
- if(!.)
- return
- var/source = args[1]
-
- if(GLOB.global_listen_count[source])
- cleanup_global_listener(source, GLOB.global_listen_count[source])
- if(GLOB.event_sources_count[source])
- cleanup_source_listeners(source, GLOB.event_sources_count[source])
- if(GLOB.event_listen_count[source])
- cleanup_event_listener(source, GLOB.event_listen_count[source])
-
-
-/decl/observ/register(var/datum/event_source, var/datum/listener, var/proc_call)
- . = ..()
- if(.)
- GLOB.event_sources_count[event_source] += 1
- GLOB.event_listen_count[listener] += 1
-
-/decl/observ/unregister(var/datum/event_source, var/datum/listener, var/proc_call)
- . = ..()
- if(.)
- GLOB.event_sources_count[event_source] -= 1
- GLOB.event_listen_count[listener] -= 1
-
-/decl/observ/register_global(var/datum/listener, var/proc_call)
- . = ..()
- if(.)
- GLOB.global_listen_count[listener] += 1
-
-/decl/observ/unregister_global(var/datum/listener, var/proc_call)
- . = ..()
- if(.)
- GLOB.global_listen_count[listener] -= 1
-
-/decl/observ/destroyed/proc/cleanup_global_listener(listener, listen_count)
- GLOB.global_listen_count -= listener
- for(var/decl/observ/event as anything in GLOB.all_observable_events.events)
- if(event.unregister_global(listener))
- // log_debug("[event] - [listener] was deleted while still registered to global events.") // TODO: Apply axe, reimplement with datum component listeners
- if(!(--listen_count))
- return
-
-/decl/observ/destroyed/proc/cleanup_source_listeners(event_source, source_listener_count)
- GLOB.event_sources_count -= event_source
- for(var/decl/observ/event as anything in GLOB.all_observable_events.events)
- var/proc_owners = event.event_sources[event_source]
- if(proc_owners)
- for(var/proc_owner in proc_owners)
- if(event.unregister(event_source, proc_owner))
- // log_debug("[event] - [event_source] was deleted while still being listened to by [proc_owner].") // TODO: Apply axe, reimplement with datum component listeners
- if(!(--source_listener_count))
- return
-
-/decl/observ/destroyed/proc/cleanup_event_listener(listener, listener_count)
- GLOB.event_listen_count -= listener
- for(var/decl/observ/event as anything in GLOB.all_observable_events.events)
- for(var/event_source in event.event_sources)
- if(event.unregister(event_source, listener))
- // log_debug("[event] - [listener] was deleted while still listening to [event_source].") // TODO: Apply axe, reimplement with datum component listeners
- if(!(--listener_count))
- return
\ No newline at end of file
diff --git a/code/datums/orbit.dm b/code/datums/orbit.dm
index adbd166d25a..745d16053e1 100644
--- a/code/datums/orbit.dm
+++ b/code/datums/orbit.dm
@@ -105,7 +105,7 @@
/atom/movable/proc/stop_orbit()
SpinAnimation(0,0)
- qdel(orbiting)
+ QDEL_NULL(orbiting)
/atom/Destroy(force = FALSE)
. = ..()
diff --git a/code/datums/outfits/jobs/civilian_vr.dm b/code/datums/outfits/jobs/civilian_vr.dm
index 55a66114848..74e441e5eef 100644
--- a/code/datums/outfits/jobs/civilian_vr.dm
+++ b/code/datums/outfits/jobs/civilian_vr.dm
@@ -23,4 +23,70 @@
/decl/hierarchy/outfit/job/assistant/entrepreneur
id_type = /obj/item/weapon/card/id/civilian/entrepreneur
l_hand = /obj/item/device/ticket_printer/train
+ uniform = /obj/item/clothing/under/tropical/pink
+/decl/hierarchy/outfit/job/assistant/entrepreneur/lawyer
+ uniform = /obj/item/clothing/under/lawyer/red
+ r_hand = /obj/item/weapon/clipboard
+ l_pocket = /obj/item/weapon/pen/fountain3
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/private_eye
+ l_hand = /obj/item/weapon/storage/box/private_investigator
+ suit = /obj/item/clothing/suit/storage/trench
+ head = /obj/item/clothing/head/fedora/brown
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/bodyguard
+ glasses = /obj/item/clothing/glasses/sunglasses
+ l_pocket = /obj/item/weapon/reagent_containers/spray/pepper
+ suit = /obj/item/clothing/accessory/sweater/blackneck
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/personal_physician
+ suit = /obj/item/clothing/suit/storage/toggle/labcoat
+ l_pocket = /obj/item/clothing/accessory/stethoscope
+ r_pocket = /obj/item/device/healthanalyzer
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/dentist
+ l_hand = /obj/item/weapon/storage/box/dentist
+ suit = /obj/item/clothing/suit/storage/toggle/labcoat
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/fitness_instructor
+ l_hand = /obj/item/weapon/storage/box/fitness_trainer
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/yoga_teacher
+ uniform = /obj/item/clothing/under/pants/yogapants
+ l_hand = /obj/item/weapon/storage/box/yoga_teacher
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/masseuse
+ r_hand = /obj/item/roller/massage
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/tradesperson
+ r_hand = /obj/item/weapon/storage/toolbox/brass
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/streamer
+ l_pocket = /obj/item/device/tvcamera/streamer
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/influencer
+ l_pocket = /obj/item/device/camera/selfie
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/paranormal_investigator
+ l_hand = /obj/item/weapon/storage/box/paranormal_investigator
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/stylist
+ l_hand = /obj/item/weapon/storage/box/stylist
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/fisher
+ r_hand = /obj/item/weapon/material/fishing_rod/modern
+ l_pocket = /obj/item/weapon/material/fishing_net
+ head = /obj/item/clothing/head/fishing
+ r_pocket = /obj/item/weapon/storage/box/wormcan
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/personal_secretary
+ uniform = /obj/item/clothing/under/lawyer/blue
+ r_hand = /obj/item/weapon/clipboard
+ l_pocket = /obj/item/weapon/pen/fountain3
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/fortune_teller
+ l_hand = /obj/item/weapon/storage/box/fortune_teller
+
+/decl/hierarchy/outfit/job/assistant/entrepreneur/spirit_healer
+ l_hand = /obj/item/weapon/storage/box/spirit_healer
diff --git a/code/datums/outfits/outfit.dm b/code/datums/outfits/outfit.dm
index ef7e9ea8ee9..b27093934e0 100644
--- a/code/datums/outfits/outfit.dm
+++ b/code/datums/outfits/outfit.dm
@@ -127,7 +127,8 @@ var/list/outfits_decls_by_type_
if(gloves)
H.equip_to_slot_or_del(new gloves(H),slot_gloves)
if(shoes)
- H.equip_to_slot_or_del(new shoes(H),slot_shoes)
+ if(!(H.client?.prefs?.shoe_hater)) //RS ADD
+ H.equip_to_slot_or_del(new shoes(H),slot_shoes)
if(mask)
H.equip_to_slot_or_del(new mask(H),slot_wear_mask)
if(head)
diff --git a/code/datums/uplink/badassery.dm b/code/datums/uplink/badassery.dm
index 8208894af4f..2541bac5045 100644
--- a/code/datums/uplink/badassery.dm
+++ b/code/datums/uplink/badassery.dm
@@ -91,4 +91,4 @@
var/obj/structure/largecrate/C = /obj/structure/largecrate
icon = image(initial(C.icon), initial(C.icon_state))
- return "\icon[icon][bicon(icon)]"
\ No newline at end of file
+ return "[bicon(icon)]"
diff --git a/code/datums/uplink/uplink_items.dm b/code/datums/uplink/uplink_items.dm
index 7fcdbd8dc2a..7b4fbded2b5 100644
--- a/code/datums/uplink/uplink_items.dm
+++ b/code/datums/uplink/uplink_items.dm
@@ -130,7 +130,7 @@ var/datum/uplink/uplink = new()
/datum/uplink_item/item/log_icon()
var/obj/I = path
- return "\icon[I][bicon(I)]"
+ return "[bicon(I)]"
/********************************
* *
@@ -144,7 +144,7 @@ var/datum/uplink/uplink = new()
if(!default_abstract_uplink_icon)
default_abstract_uplink_icon = image('icons/obj/pda.dmi', "pda-syn")
- return "\icon[default_abstract_uplink_icon][bicon(default_abstract_uplink_icon)]"
+ return "[bicon(default_abstract_uplink_icon)]"
/*
* Crated goods.
@@ -174,7 +174,7 @@ var/datum/uplink/uplink = new()
/datum/uplink_item/crated/log_icon()
var/obj/I = crate_path
- return "\icon[I]"
+ return "[bicon(I)]"
/****************
* Support procs *
diff --git a/code/datums/wires/camera.dm b/code/datums/wires/camera.dm
index c572ad8d27c..322478688c3 100644
--- a/code/datums/wires/camera.dm
+++ b/code/datums/wires/camera.dm
@@ -57,7 +57,7 @@
C.light_disabled = !C.light_disabled
if(WIRE_CAM_ALARM)
- C.visible_message("\icon[C][bicon(C)] *beep*", "\icon[C][bicon(C)] *beep*")
+ C.visible_message("[icon2html(C,viewers(holder))] *beep*", "[icon2html(C,viewers(holder))] *beep*")
..()
/datum/wires/camera/proc/CanDeconstruct()
diff --git a/code/datums/wires/jukebox.dm b/code/datums/wires/jukebox.dm
index cec0f4d370b..5f46a3deb96 100644
--- a/code/datums/wires/jukebox.dm
+++ b/code/datums/wires/jukebox.dm
@@ -31,16 +31,16 @@
var/obj/machinery/media/jukebox/A = holder
switch(wire)
if(WIRE_MAIN_POWER1)
- holder.visible_message("\icon[holder][bicon(holder)] The power light flickers.")
+ holder.visible_message("[icon2html(A,viewers(holder))] The power light flickers.")
A.shock(usr, 90)
if(WIRE_JUKEBOX_HACK)
- holder.visible_message("\icon[holder][bicon(holder)] The parental guidance light flickers.")
+ holder.visible_message("[icon2html(A,viewers(holder))] The parental guidance light flickers.")
if(WIRE_REVERSE)
- holder.visible_message("\icon[holder][bicon(holder)] The data light blinks ominously.")
+ holder.visible_message("[icon2html(A,viewers(holder))] The data light blinks ominously.")
if(WIRE_SPEEDUP)
- holder.visible_message("\icon[holder][bicon(holder)] The speakers squeaks.")
+ holder.visible_message("[icon2html(A,viewers(holder))] The speakers squeaks.")
if(WIRE_SPEEDDOWN)
- holder.visible_message("\icon[holder][bicon(holder)] The speakers rumble.")
+ holder.visible_message("[icon2html(A,viewers(holder))] The speakers rumble.")
if(WIRE_START)
A.StartPlaying()
if(WIRE_STOP)
diff --git a/code/datums/wires/mines.dm b/code/datums/wires/mines.dm
index 55ea718c3f4..229ce682be8 100644
--- a/code/datums/wires/mines.dm
+++ b/code/datums/wires/mines.dm
@@ -21,15 +21,15 @@
switch(wire)
if(WIRE_EXPLODE)
- C.visible_message("\icon[C][bicon(C)] *BEEE-*", "\icon[C][bicon(C)] *BEEE-*")
+ C.visible_message("[icon2html(C,viewers(holder))] *BEEE-*", "[icon2html(C,viewers(holder))] *BEEE-*")
C.explode()
if(WIRE_EXPLODE_DELAY)
- C.visible_message("\icon[C][bicon(C)] *BEEE-*", "\icon[C][bicon(C)] *BEEE-*")
+ C.visible_message("[icon2html(C,viewers(holder))] *BEEE-*", "[icon2html(C,viewers(holder))] *BEEE-*")
C.explode()
if(WIRE_DISARM)
- C.visible_message("\icon[C][bicon(C)] *click!*", "\icon[C][bicon(C)] *click!*")
+ C.visible_message("[icon2html(C,viewers(holder))] *click!*", "[icon2html(C,viewers(holder))] *click!*")
var/obj/effect/mine/MI = new C.mineitemtype(get_turf(C))
if(C.trap)
@@ -41,15 +41,15 @@
qdel(C)
if(WIRE_BADDISARM)
- C.visible_message("\icon[C][bicon(C)] *BEEPBEEPBEEP*", "\icon[C][bicon(C)] *BEEPBEEPBEEP*")
+ C.visible_message("[icon2html(C,viewers(holder))] *BEEPBEEPBEEP*", "[icon2html(C,viewers(holder))] *BEEPBEEPBEEP*")
spawn(20)
C.explode()
if(WIRE_TRAP)
- C.visible_message("\icon[C][bicon(C)] *click!*", "\icon[C][bicon(C)] *click!*")
+ C.visible_message("[icon2html(C,viewers(holder))] *click!*", "[icon2html(C,viewers(holder))] *click!*")
if(mend)
- C.visible_message("\icon[C][bicon(C)] - The mine recalibrates[C.camo_net ? ", revealing \the [C.trap] inside." : "."]")
+ C.visible_message("[icon2html(C,viewers(holder))] - The mine recalibrates[C.camo_net ? ", revealing \the [C.trap] inside." : "."]")
C.alpha = 255
@@ -61,21 +61,21 @@
return
switch(wire)
if(WIRE_EXPLODE)
- C.visible_message("\icon[C][bicon(C)] *beep*", "\icon[C][bicon(C)] *beep*")
+ C.visible_message("[icon2html(C,viewers(holder))] *beep*", "[icon2html(C,viewers(holder))] *beep*")
if(WIRE_EXPLODE_DELAY)
- C.visible_message("\icon[C][bicon(C)] *BEEPBEEPBEEP*", "\icon[C][bicon(C)] *BEEPBEEPBEEP*")
+ C.visible_message("[icon2html(C,viewers(holder))] *BEEPBEEPBEEP*", "[icon2html(C,viewers(holder))] *BEEPBEEPBEEP*")
spawn(20)
C.explode()
if(WIRE_DISARM)
- C.visible_message("\icon[C][bicon(C)] *ping*", "\icon[C][bicon(C)] *ping*")
+ C.visible_message("[icon2html(C,viewers(holder))] *ping*", "[icon2html(C,viewers(holder))] *ping*")
if(WIRE_BADDISARM)
- C.visible_message("\icon[C][bicon(C)] *ping*", "\icon[C][bicon(C)] *ping*")
+ C.visible_message("[icon2html(C,viewers(holder))] *ping*", "[icon2html(C,viewers(holder))] *ping*")
if(WIRE_TRAP)
- C.visible_message("\icon[C][bicon(C)] *ping*", "\icon[C][bicon(C)] *ping*")
+ C.visible_message("[icon2html(C,viewers(holder))] *ping*", "[icon2html(C,viewers(holder))] *ping*")
..()
diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm
index 1787039622b..1b1f03bd193 100644
--- a/code/datums/wires/particle_accelerator.dm
+++ b/code/datums/wires/particle_accelerator.dm
@@ -26,7 +26,7 @@
C.interface_control = !C.interface_control
if(WIRE_PARTICLE_POWER_LIMIT)
- C.visible_message("\icon[C][bicon(C)][C] makes a large whirring noise.")
+ C.visible_message("[icon2html(C,viewers(holder))][C] makes a large whirring noise.")
/datum/wires/particle_acc/control_box/on_cut(wire, mend)
var/obj/machinery/particle_accelerator/control_box/C = holder
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index aab9326c519..746b6a19ede 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -160,7 +160,7 @@
ASSERT(isturf(loc))
var/list/turfs = trange(range, src)
for(var/turf/T as anything in turfs)
- GLOB.turf_entered_event.register(T, src, callback)
+ RegisterSignal(T, COMSIG_OBSERVER_TURF_ENTERED, callback)
//Unregister from prox listening in a certain range. You should do this BEFORE you move, but if you
// really can't, then you can set the center where you moved from.
@@ -168,7 +168,7 @@
ASSERT(isturf(center) || isturf(loc))
var/list/turfs = trange(range, center ? center : src)
for(var/turf/T as anything in turfs)
- GLOB.turf_entered_event.unregister(T, src, callback)
+ UnregisterSignal(T, COMSIG_OBSERVER_TURF_ENTERED)
/atom/proc/emp_act(var/severity)
@@ -235,7 +235,7 @@
else
f_name += "oil-stained [name][infix]."
- var/list/output = list("\icon[src.examine_icon()][bicon(src)] That's [f_name] [suffix]", get_examine_desc())
+ var/list/output = list("[icon2html(src,user.client)] That's [f_name] [suffix]", get_examine_desc())
if(user.client?.prefs.examine_text_mode == EXAMINE_MODE_INCLUDE_USAGE)
output += description_info
@@ -712,7 +712,7 @@
/atom/Entered(atom/movable/AM, atom/old_loc)
. = ..()
- GLOB.moved_event.raise_event(AM, old_loc, AM.loc)
+ SEND_SIGNAL(AM, COMSIG_OBSERVER_MOVED, old_loc, AM.loc)
SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, old_loc)
SEND_SIGNAL(AM, COMSIG_ATOM_ENTERING, src, old_loc)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index e4643c0bb21..9c63bcb7f73 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -374,7 +374,7 @@
return TRUE
/atom/movable/proc/onTransitZ(old_z,new_z)
- GLOB.z_moved_event.raise_event(src, old_z, new_z)
+ SEND_SIGNAL(src, COMSIG_OBSERVER_Z_MOVED, old_z, new_z)
SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_z, new_z)
for(var/atom/movable/AM as anything in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care.
AM.onTransitZ(old_z,new_z)
diff --git a/code/game/jobs/job/civilian_vr.dm b/code/game/jobs/job/civilian_vr.dm
index e9f105637ed..31d69c1a893 100644
--- a/code/game/jobs/job/civilian_vr.dm
+++ b/code/game/jobs/job/civilian_vr.dm
@@ -337,70 +337,84 @@
/datum/alt_title/lawyer
title = "Lawyer"
title_blurb = "A Lawyer is knowledgable in various legal systems, including station's operations. They can try to offer their legal counsel, although nobody is really obliged to listen."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/personal_secretary
/datum/alt_title/private_eye
title = "Private Eye"
title_blurb = "A Private Eye is a detective that has no credentials or equipment. But if someone wants something found without security's knowledge, they are the one to go to."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/private_eye
/datum/alt_title/bodyguard
title = "Bodyguard"
title_blurb = "A Bodyguard offers service of personal protection. They may not be allowed any weapons, but their own body is weapon enough."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/bodyguard
/datum/alt_title/personal_physician
title = "Personal Physician"
title_blurb = "A Personal Physicial is a doctor dedicated less to Hippocratic Oath and more to the moneymaking grind. Their license may be expired, but the grindset never will be."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/personal_physician
/datum/alt_title/dentist
title = "Dentist"
title_blurb = "A Dentist is a doctor that specializes in oral care. Company may not recognize them as a proper doctor, but surely their customers will."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/dentist
/datum/alt_title/fitness_instructor
title = "Fitness Instructor"
title_blurb = "A Fitness Instructor dedicates themselves to improving the health of the crew through physical activity, and boy, do they need the help."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/fitness_instructor
/datum/alt_title/yoga_teacher
title = "Yoga Teacher"
title_blurb = "A Yoga Teacher is similar to fitness instructor, but rather than turning the round bodies into firm ones, they focus on helping people find balance and harmony."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/yoga_teacher
/datum/alt_title/masseuse
title = "Masseuse"
title_blurb = "A Masseuse is master of physical therapy and working others' bodies with their hands."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/masseuse
/datum/alt_title/tradesperson
title = "Tradesperson"
title_blurb = "A Tradesperson is someone attempting to make money via the most obvious act of all - buying and selling. Now if only customs allowed you to bring your goods along..."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/tradesperson
/datum/alt_title/streamer
title = "Streamer"
title_blurb = "A Streamer is here to entertain. Not the crew! Their audience across exonet!"
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/streamer
/datum/alt_title/influencer
title = "Influencer"
title_blurb = "An Influencer has lucked out with some exonet following, and was given permission to go onstation to provide free exposure. Don't let it go to your head."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/influencer
/datum/alt_title/paranormal_investigator
title = "Paranormal Investigator"
title_blurb = "A Paranormal Investigator looks beyond what is accepted by modern science, and searches for the true unknown. Aliens, alternate dimensions, ghosts... The truth is out there!"
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/paranormal_investigator
/datum/alt_title/personal_secretary
title = "Personal Secretary"
title_blurb = "A Personal Secretary offers services of general assistance. Although it's doubtful anyone will ever actually need those."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/personal_secretary
/datum/alt_title/stylist
title = "Stylist"
title_blurb = "A Stylist offers fashion advice, as well as helps with adjusting appearance of the crew to better suit their beauty standards."
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/stylist
/datum/alt_title/fisher
title = "Fisher"
title_blurb = "A Fisher is a capable angler, who is good at obtaining large amounts of marine goods. Whether you generously give them to station or attempt to make a quick thaler by selling, it's up to you!"
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/fisher
/datum/alt_title/fortune_teller
title = "Fortune Teller"
title_blurb = "A Fortune Teller peers into the future, and offers these visions to others. Occasionally those visions may even come true!"
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/fortune_teller
/datum/alt_title/spirit_healer
title = "Spirit Healer"
title_blurb = "A Spirit Healer offers alternative forms of medicine. Rituals, magic rocks, seances... It totally works. What's that about placebo?"
-
-
-
+ title_outfit = /decl/hierarchy/outfit/job/assistant/entrepreneur/spirit_healer
diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm
index 8f1f409b66a..6091fc0e7b3 100644
--- a/code/game/jobs/job_controller.dm
+++ b/code/game/jobs/job_controller.dm
@@ -430,6 +430,8 @@ var/global/datum/controller/occupations/job_master
//if(G.slot == slot_wear_mask || G.slot == slot_wear_suit || G.slot == slot_head)
// custom_equip_leftovers += thing
//else
+ if(G.slot == slot_shoes && H.client?.prefs?.shoe_hater) //RS ADD
+ continue
if(H.equip_to_slot_or_del(G.spawn_item(H, metadata), G.slot))
to_chat(H, "Equipping you with \the [thing]!")
if(G.slot != slot_tie)
@@ -455,6 +457,8 @@ var/global/datum/controller/occupations/job_master
// If some custom items could not be equipped before, try again now.
for(var/thing in custom_equip_leftovers)
var/datum/gear/G = gear_datums[thing]
+ if(G.slot == slot_shoes && H.client?.prefs?.shoe_hater) //RS ADD
+ continue
if(G.slot in custom_equip_slots)
spawn_in_storage += thing
else
diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm
index 28255c395ad..5a301b79142 100644
--- a/code/game/machinery/cell_charger.dm
+++ b/code/game/machinery/cell_charger.dm
@@ -27,13 +27,10 @@
var/newlevel = round(charging.percent() * 4.0 / 99)
//to_world("nl: [newlevel]")
- if(chargelevel != newlevel)
-
- cut_overlays()
- add_overlay("ccharger-o[newlevel]")
-
- chargelevel = newlevel
+ cut_overlays()
+ add_overlay("ccharger-o[newlevel]")
+ chargelevel = newlevel
add_overlay(image(charging.icon, charging.icon_state))
add_overlay("ccharger-[charging.connector_type]-on")
@@ -126,10 +123,11 @@
return
if(charging && !charging.fully_charged())
+ var/newlevel = round(charging.percent() * 4.0 / 99)
charging.give(efficiency*CELLRATE)
update_use_power(USE_POWER_ACTIVE)
-
- update_icon()
+ if(chargelevel != newlevel)
+ update_icon()
else
update_use_power(USE_POWER_IDLE)
@@ -137,4 +135,4 @@
var/E = 0
for(var/obj/item/weapon/stock_parts/capacitor/C in component_parts)
E += C.rating
- efficiency = active_power_usage * (1+ (E - 1)*0.5)
\ No newline at end of file
+ efficiency = active_power_usage * (1+ (E - 1)*0.5)
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 1834971be2d..d6dd254de14 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -1125,7 +1125,7 @@
// This is not a status display message, since it's something the character
// themselves is meant to see BEFORE putting the money in
- to_chat(usr, "\icon[cashmoney][bicon(cashmoney)] That is not enough money.")
+ to_chat(usr, "[icon2html(cashmoney,user.client)] That is not enough money.")
return 0
if(istype(cashmoney, /obj/item/weapon/spacecash))
@@ -1338,4 +1338,4 @@
to_chat(user, "You turn in 2 tickets to the [src] and claim a prize!")
return
else
- ..() //You can now actually deconstruct these.
\ No newline at end of file
+ ..() //You can now actually deconstruct these.
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index c5680602ddc..9386f531d6e 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -1141,7 +1141,11 @@ About the new airlock wires panel:
if(istype(C, /mob/living))
..()
return
- if(!repairing && C.has_tool_quality(TOOL_WELDER) && !( src.operating > 0 ) && src.density)
+ //VOREstation Edit: Removing material cost from repair requirements
+ if(C.has_tool_quality(TOOL_WELDER) && !( src.operating > 0 ) && src.density)
+ if(health < maxhealth && user.a_intent == I_HELP)
+ ..()
+ return
var/obj/item/weapon/weldingtool/W = C.get_welder()
if(W.remove_fuel(0,user))
if(!src.welded)
@@ -1176,7 +1180,7 @@ About the new airlock wires panel:
else if(istype(C, /obj/item/weapon/pai_cable)) // -- TLE
var/obj/item/weapon/pai_cable/cable = C
cable.plugin(src, user)
- else if(!repairing && C.has_tool_quality(TOOL_CROWBAR))
+ else if(C.has_tool_quality(TOOL_CROWBAR))
if(can_remove_electronics())
playsound(src, C.usesound, 75, 1)
user.visible_message("[user] removes the electronics from the airlock assembly.", "You start to remove electronics from the airlock assembly.")
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 5425b712109..3493ec1dc74 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -28,7 +28,7 @@
var/destroy_hits = 10 //How many strong hits it takes to destroy the door
var/min_force = 10 //minimum amount of force needed to damage the door with a melee weapon
var/hitsound = 'sound/weapons/smash.ogg' //sound door makes when hit with a weapon
- var/repairing = 0
+ //var/repairing = 0 //VOREstation Edit: We're not using materials anymore
var/block_air_zones = 1 //If set, air zones cannot merge across the door even when it is opened.
var/close_door_at = 0 //When to automatically close the door, if possible
@@ -226,63 +226,21 @@
if(istype(I))
if(attackby_vr(I, user)) //VOREStation begin: Fireproofing
return //VOREStation begin: Fireproofing
- if(istype(I, /obj/item/stack/material) && I.get_material_name() == src.get_material_name())
- if(stat & BROKEN)
- to_chat(user, "It looks like \the [src] is pretty busted. It's going to need more than just patching up now.")
- return
- if(health >= maxhealth)
- to_chat(user, "Nothing to fix!")
- return
- if(!density)
- to_chat(user, "\The [src] must be closed before you can repair it.")
- return
-
- //figure out how much metal we need
- var/amount_needed = (maxhealth - health) / DOOR_REPAIR_AMOUNT
- amount_needed = (round(amount_needed) == amount_needed)? amount_needed : round(amount_needed) + 1 //Why does BYOND not have a ceiling proc?
-
- var/obj/item/stack/stack = I
- var/amount_given = amount_needed - repairing
- var/mats_given = stack.get_amount()
- if(repairing && amount_given <= 0)
- to_chat(user, "You must weld or remove \the [get_material_name()] from \the [src] before you can add anything else.")
- else
- if(mats_given >= amount_given)
- if(stack.use(amount_given))
- repairing += amount_given
- else
- if(stack.use(mats_given))
- repairing += mats_given
- amount_given = mats_given
- if(amount_given)
- to_chat(user, "You fit [amount_given] [stack.singular_name]\s to damaged and broken parts on \the [src].")
-
- return
-
- if(repairing && I.has_tool_quality(TOOL_WELDER))
+ if(health < maxhealth && I.has_tool_quality(TOOL_WELDER))
if(!density)
to_chat(user, "\The [src] must be closed before you can repair it.")
return
var/obj/item/weapon/weldingtool/welder = I.get_welder()
if(welder.remove_fuel(0,user))
- to_chat(user, "You start to fix dents and weld \the [get_material_name()] into place.")
+ to_chat(user, "You start to fix dents and repair \the [src].")
playsound(src, welder.usesound, 50, 1)
- if(do_after(user, (5 * repairing) * welder.toolspeed) && welder && welder.isOn())
+ var/repairtime = maxhealth - health //Since we're not using materials anymore... We'll just calculate how much damage there is to repair.
+ if(do_after(user, repairtime * welder.toolspeed) && welder && welder.isOn())
to_chat(user, "You finish repairing the damage to \the [src].")
- health = between(health, health + repairing*DOOR_REPAIR_AMOUNT, maxhealth)
+ health = maxhealth
update_icon()
- repairing = 0
return
-
- if(repairing && I.has_tool_quality(TOOL_CROWBAR))
- var/datum/material/mat = get_material()
- var/obj/item/stack/material/repairing_sheet = mat.place_sheet(loc, repairing)
- repairing = 0
- to_chat(user, "You remove \the [repairing_sheet].")
- playsound(src, I.usesound, 100, 1)
- return
-
//psa to whoever coded this, there are plenty of objects that need to call attack() on doors without bludgeoning them.
if(src.density && istype(I, /obj/item/weapon) && user.a_intent == I_HURT && !istype(I, /obj/item/weapon/card))
var/obj/item/weapon/W = I
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 22b6f37d60b..b2ec1627ca3 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -248,7 +248,12 @@
return //Don't open the door if we're putting tape on it to tell people 'don't open the door'.
if(operating)
return//Already doing something.
- if(C.has_tool_quality(TOOL_WELDER) && !repairing)
+ if(C.has_tool_quality(TOOL_WELDER))
+ //VOREstation Edit: Removing Material requirements on repairs
+ if(health < maxhealth)
+ ..()
+ return
+ //VOREstation Edit End
if(prying)
to_chat(user, "Someone's busy prying that [density ? "open" : "closed"]!")
var/obj/item/weapon/weldingtool/W = C.get_welder()
@@ -269,7 +274,7 @@
update_icon()
return
- if(blocked && C.has_tool_quality(TOOL_CROWBAR) && !repairing)
+ if(blocked && C.has_tool_quality(TOOL_CROWBAR))
if(!hatch_open)
to_chat(user, "You must open the maintenance hatch first!")
else
diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm
index 5340bd2dd5e..29174f7e742 100644
--- a/code/game/machinery/lightswitch.dm
+++ b/code/game/machinery/lightswitch.dm
@@ -86,4 +86,14 @@
..(severity)
return
power_change()
- ..(severity)
\ No newline at end of file
+ ..(severity)
+
+//Breakers for event maps
+
+/obj/machinery/light_switch/breaker
+ name = "lights breaker"
+ desc = "A breaker for controlling power to the lights connected to the circuit."
+ icon = 'icons/obj/power_breaker.dmi'
+ icon_state = "light1"
+ on = 0
+
diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm
index b275821a593..174fdf630d6 100644
--- a/code/game/machinery/machinery.dm
+++ b/code/game/machinery/machinery.dm
@@ -284,7 +284,7 @@ Class Procs:
/obj/machinery/proc/state(var/msg)
for(var/mob/O in hearers(src, null))
- O.show_message("\icon[src][bicon(src)] [msg]", 2)
+ O.show_message("[icon2html(src,O.client)] [msg]", 2)
/obj/machinery/proc/ping(text=null)
if(!text)
diff --git a/code/game/machinery/machinery_power.dm b/code/game/machinery/machinery_power.dm
index 0f4c2ef01b1..c5c6c6c03c7 100644
--- a/code/game/machinery/machinery_power.dm
+++ b/code/game/machinery/machinery_power.dm
@@ -73,14 +73,18 @@
// Do not do power stuff in New/Initialize until after ..()
/obj/machinery/Initialize()
. = ..()
+ RegisterSignal(src, COMSIG_OBSERVER_MOVED, PROC_REF(update_power_on_move))
+ AddComponent(/datum/component/recursive_move)
var/power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(0, power)
power_init_complete = TRUE
// Or in Destroy at all, but especially after the ..().
/obj/machinery/Destroy()
+ /*
if(ismovable(loc))
- GLOB.moved_event.unregister(loc, src, PROC_REF(update_power_on_move)) // Unregister just in case
+ UnregisterSignal(loc, COMSIG_OBSERVER_MOVED) // Unregister just in case
+ */
var/power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(power, 0)
. = ..()
@@ -90,10 +94,12 @@
/obj/machinery/Moved(atom/old_loc, direction, forced = FALSE)
. = ..()
update_power_on_move(src, old_loc, loc)
- if(ismovable(loc)) // Register for recursive movement (if the thing we're inside moves)
- GLOB.moved_event.register(loc, src, PROC_REF(update_power_on_move))
+ /* No
if(ismovable(old_loc)) // Unregister recursive movement.
- GLOB.moved_event.unregister(old_loc, src, PROC_REF(update_power_on_move))
+ UnregisterSignal(old_loc, COMSIG_OBSERVER_MOVED)
+ if(ismovable(loc)) // Register for recursive movement (if the thing we're inside moves)
+ RegisterSignal(loc, COMSIG_OBSERVER_MOVED, PROC_REF(update_power_on_move), override = TRUE)
+ */
/obj/machinery/proc/update_power_on_move(atom/movable/mover, atom/old_loc, atom/new_loc)
var/area/old_area = get_area(old_loc)
diff --git a/code/game/machinery/overview.dm b/code/game/machinery/overview.dm
index 058abb899d7..027de127b26 100644
--- a/code/game/machinery/overview.dm
+++ b/code/game/machinery/overview.dm
@@ -138,7 +138,7 @@
var/icon/I = imap[1+(ix + icx*iy)*2]
var/icon/I2 = imap[2+(ix + icx*iy)*2]
- //to_world("icon: \icon[I][bicon(I)]")
+ //to_world("icon: [icon2html(I)]")
I.DrawBox(colour, rx, ry, rx+1, ry+1)
@@ -153,7 +153,7 @@
H.screen_loc = "[5 + i%icx],[6+ round(i/icx)]"
- //to_world("\icon[I][bicon(I)] at [H.screen_loc]")
+ //to_world("[icon2html(I)] at [H.screen_loc]")
H.name = (i==0)?"maprefresh":"map"
@@ -266,7 +266,7 @@
//to_world("trying [ix],[iy] : [ix+icx*iy]")
var/icon/I = imap[1+(ix + icx*iy)]
- //to_world("icon: \icon[I][bicon(I)]")
+ //to_world("icon: [icon2html(I)]")
I.DrawBox(colour, rx, ry, rx, ry)
@@ -279,7 +279,7 @@
H.screen_loc = "[5 + i%icx],[6+ round(i/icx)]"
- //to_world("\icon[I][bicon(I)] at [H.screen_loc]")
+ //to_world("[icon2html(I)] at [H.screen_loc]")
H.name = (i==0)?"maprefresh":"map"
@@ -332,4 +332,4 @@
qdel(O)
mapobjs = null
- src.unset_machine()
\ No newline at end of file
+ src.unset_machine()
diff --git a/code/game/machinery/partslathe_vr.dm b/code/game/machinery/partslathe_vr.dm
index 3fe7074b419..aef7fc1ee34 100644
--- a/code/game/machinery/partslathe_vr.dm
+++ b/code/game/machinery/partslathe_vr.dm
@@ -163,7 +163,7 @@
removeFromQueue(1)
update_icon()
else if(busy)
- visible_message("\icon [src] flashes: insufficient materials: [getLackingMaterials(D)].")
+ visible_message("[icon2html(src,viewers(src))] flashes: insufficient materials: [getLackingMaterials(D)].")
busy = 0
update_use_power(USE_POWER_IDLE)
update_icon()
@@ -279,7 +279,7 @@
var/datum/category_item/partslathe/current = queue[1]
data["building"] = current.name
data["buildPercent"] = (progress / current.time * 100)
-
+
data["error"] = null
if(queue.len > 0 && !canBuild(queue[1]))
data["error"] = getLackingMaterials(queue[1])
diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm
index 0a9def34461..451472c7bf6 100644
--- a/code/game/machinery/portable_turret.dm
+++ b/code/game/machinery/portable_turret.dm
@@ -254,9 +254,6 @@
if(get_dist(src, L) > 7) //if it's too far away, why bother?
return TURRET_NOT_TARGET
- if(!(L in check_trajectory(L, src))) //check if we have true line of sight
- return TURRET_NOT_TARGET
-
if(L.lying) //Don't need to stun-lock the players
return TURRET_NOT_TARGET
@@ -726,9 +723,6 @@
if(get_dist(src, L) > 7) //if it's too far away, why bother?
return TURRET_NOT_TARGET
- if(!(L in check_trajectory(L, src))) //check if we have true line of sight
- return TURRET_NOT_TARGET
-
if(emagged) // If emagged not even the dead get a rest
return L.stat ? TURRET_SECONDARY_TARGET : TURRET_PRIORITY_TARGET
@@ -841,13 +835,14 @@
if(disabled)
return FALSE
if(target)
- last_target = target
- popUp() //pop the turret up if it's not already up.
- set_dir(get_dir(src, target)) //even if you can't shoot, follow the target
- playsound(src, 'sound/machines/turrets/turret_rotate.ogg', 100, 1) // Play rotating sound
- spawn()
- shootAt(target)
- return TRUE
+ if(target in check_trajectory(target, src)) //Finally, check if we can actually hit the target
+ last_target = target
+ popUp() //pop the turret up if it's not already up.
+ set_dir(get_dir(src, target)) //even if you can't shoot, follow the target
+ playsound(src, 'sound/machines/turrets/turret_rotate.ogg', 100, 1) // Play rotating sound
+ spawn()
+ shootAt(target)
+ return TRUE
return FALSE
/obj/machinery/porta_turret/proc/shootAt(var/mob/living/target)
diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm
index 9e9e8786468..11eb234e26d 100644
--- a/code/game/machinery/requests_console.dm
+++ b/code/game/machinery/requests_console.dm
@@ -198,7 +198,7 @@ var/list/obj/machinery/requests_console/allConsoles = list()
screen = RCS_SENTPASS
message_log += list(list("Message sent to [recipient]", "[message]"))
else
- audible_message(text("\icon[src][bicon(src)] *The Requests Console beeps: 'NOTICE: No server detected!'"),,4)
+ audible_message(text("[icon2html(src,viewers(src))] *The Requests Console beeps: 'NOTICE: No server detected!'"),,4)
. = TRUE
//Handle printing
diff --git a/code/game/machinery/suit_storage/suit_cycler.dm b/code/game/machinery/suit_storage/suit_cycler.dm
index cdd6b45dc29..6803194eef7 100644
--- a/code/game/machinery/suit_storage/suit_cycler.dm
+++ b/code/game/machinery/suit_storage/suit_cycler.dm
@@ -477,7 +477,7 @@ GLOBAL_LIST_EMPTY(suit_cycler_typecache)
/obj/machinery/suit_cycler/proc/finished_job()
var/turf/T = get_turf(src)
- T.visible_message("\icon[src][bicon(src)]The [src] beeps several times.")
+ T.visible_message("[icon2html(src,viewers(src))]The [src] beeps several times.")
icon_state = initial(icon_state)
active = 0
playsound(src, 'sound/machines/boobeebeep.ogg', 50)
@@ -543,5 +543,5 @@ GLOBAL_LIST_EMPTY(suit_cycler_typecache)
if(target_species.can_refit_to(helmet, suit, suit?.helmet))
target_species.do_refit_to(helmet, suit, suit?.helmet)
else
- visible_message("\icon[src][bicon(src)]Unable to apply specified cosmetics with specified species. Please try again with a different species or cosmetic option selected.")
+ visible_message("[icon2html(src,viewers(src))]Unable to apply specified cosmetics with specified species. Please try again with a different species or cosmetic option selected.")
return
diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm
index 36ffd8f8bc4..5fee77b9e3d 100644
--- a/code/game/machinery/telecomms/broadcaster.dm
+++ b/code/game/machinery/telecomms/broadcaster.dm
@@ -455,7 +455,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
if(data == DATA_ANTAG) // intercepted radio message
part_b_extra = " (Intercepted)"
var/part_a = ""
- var/part_b = "\icon[radio][bicon(radio)]\[[freq_text]\][part_b_extra]" // goes in the actual output
+ var/part_b = "[icon2html(radio, heard_masked + heard_normal + heard_voice + heard_garbled + heard_gibberish)]\[[freq_text]\][part_b_extra]" // goes in the actual output
// --- Some more pre-message formatting ---
var/part_c = "" // Tweaked for security headsets -- TLE
@@ -658,7 +658,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
// Create a radio headset for the sole purpose of using its icon
var/obj/item/device/radio/headset/radio = new
- var/part_b = "\icon[radio][bicon(radio)]\[[freq_text]\][part_b_extra]" // Tweaked for security headsets -- TLE
+ var/part_b = "[icon2html(radio, heard_normal + heard_garbled + heard_gibberish)]\[[freq_text]\][part_b_extra]" // Tweaked for security headsets -- TLE
var/part_blackbox_b = " \[[freq_text]\]" // Tweaked for security headsets -- TLE
var/part_c = ""
diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm
index 124f5171b41..e136f087edf 100644
--- a/code/game/machinery/telecomms/machine_interactions.dm
+++ b/code/game/machinery/telecomms/machine_interactions.dm
@@ -313,13 +313,13 @@
if("freq")
- var/newfreq = input(usr, "Specify a new frequency to filter (GHz). Decimals assigned automatically.", src, network) as null|num
+ var/newfreq = tgui_input_number(usr, "Specify a new frequency to filter (GHz). Decimals assigned automatically.", src, max_value=9999)
if(newfreq && canAccess(usr))
if(findtext(num2text(newfreq), "."))
newfreq *= 10 // shift the decimal one place
if(!(newfreq in freq_listening) && newfreq < 10000)
freq_listening.Add(newfreq)
- set_temp("-% New frequency filter assigned: \"[newfreq] GHz\" %-", "average")
+ set_temp("-% New frequency filter assigned: \"[newfreq/10] GHz\" %-", "average")
. = TRUE
if("delete")
diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm
index e4a178034ea..80c16c4a654 100644
--- a/code/game/mecha/equipment/mecha_equipment.dm
+++ b/code/game/mecha/equipment/mecha_equipment.dm
@@ -272,7 +272,7 @@
/obj/item/mecha_parts/mecha_equipment/proc/occupant_message(message)
if(chassis)
- chassis.occupant_message("\icon[src][bicon(src)] [message]")
+ chassis.occupant_message("[icon2html(src, chassis.occupant.client)] [message]")
return
/obj/item/mecha_parts/mecha_equipment/proc/log_message(message)
diff --git a/code/game/mecha/equipment/tools/shield_omni.dm b/code/game/mecha/equipment/tools/shield_omni.dm
index 8ee24a77556..3d41a1cf2e3 100644
--- a/code/game/mecha/equipment/tools/shield_omni.dm
+++ b/code/game/mecha/equipment/tools/shield_omni.dm
@@ -77,7 +77,8 @@
/obj/item/shield_projector/rectangle/mecha/Initialize()
. = ..()
my_mech = loc
- GLOB.moved_event.register(my_mech, src, /obj/item/shield_projector/proc/update_shield_positions)
+ RegisterSignal(my_mech, COMSIG_OBSERVER_MOVED, /obj/item/shield_projector/proc/update_shield_positions)
+ my_mech.AddComponent(/datum/component/recursive_move)
update_shift(my_mech)
/obj/item/shield_projector/rectangle/mecha/proc/update_shift(atom/movable/mech)
@@ -88,7 +89,7 @@
shift_y = round(y_dif, 1)
/obj/item/shield_projector/rectangle/mecha/Destroy()
- GLOB.moved_event.unregister(my_mech, src, /obj/item/shield_projector/proc/update_shield_positions)
+ UnregisterSignal(my_mech, COMSIG_OBSERVER_MOVED)
my_mech = null
..()
diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm
index ebc1e22aa14..34395e0dcf7 100644
--- a/code/game/mecha/mech_fabricator.dm
+++ b/code/game/mecha/mech_fabricator.dm
@@ -664,20 +664,20 @@
switch(emagged)
if(0)
emagged = 0.5
- visible_message("\icon[src][bicon(src)] [src] beeps: \"DB error \[Code 0x00F1\]\"")
+ visible_message("[icon2html(src,viewers(src))] [src] beeps: \"DB error \[Code 0x00F1\]\"")
sleep(10)
- visible_message("\icon[src][bicon(src)] [src] beeps: \"Attempting auto-repair\"")
+ visible_message("[icon2html(src,viewers(src))] [src] beeps: \"Attempting auto-repair\"")
sleep(15)
- visible_message("\icon[src][bicon(src)] [src] beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"")
+ visible_message("[icon2html(src,viewers(src))] [src] beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"")
sleep(30)
- visible_message("\icon[src][bicon(src)] [src] beeps: \"User DB truncated. Please contact your [using_map.company_name] system operator for future assistance.\"")
+ visible_message("[icon2html(src,viewers(src))] [src] beeps: \"User DB truncated. Please contact your [using_map.company_name] system operator for future assistance.\"")
req_access = null
emagged = 1
return 1
if(0.5)
- visible_message("\icon[src][bicon(src)] [src] beeps: \"DB not responding \[Code 0x0003\]...\"")
+ visible_message("[icon2html(src,viewers(src))] [src] beeps: \"DB not responding \[Code 0x0003\]...\"")
if(1)
- visible_message("\icon[src][bicon(src)] [src] beeps: \"No records in User DB\"")
+ visible_message("[icon2html(src,viewers(src))] [src] beeps: \"No records in User DB\"")
/obj/machinery/mecha_part_fabricator/proc/eject_materials(var/material, var/amount) // 0 amount = 0 means ejecting a full stack; -1 means eject everything
var/recursive = amount == -1 ? TRUE : FALSE
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 07bba04dc1d..1e054279cab 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -538,7 +538,7 @@
if(equipment?.len)
. += "It's equipped with:"
for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment)
- . += "\icon[ME][bicon(ME)] [ME]"
+ . += "[icon2html(ME,user.client)] [ME]"
/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits.
return
@@ -2439,7 +2439,7 @@
/obj/mecha/proc/occupant_message(message as text)
if(message)
if(src.occupant && src.occupant.client)
- to_chat(src.occupant, "\icon[src][bicon(src)] [message]")
+ to_chat(src.occupant, "[icon2html(src, src.occupant.client)] [message]")
return
/obj/mecha/proc/log_message(message as text,red=null)
diff --git a/code/game/objects/effects/semirandom_mobs_vr.dm b/code/game/objects/effects/semirandom_mobs_vr.dm
index 608b333604f..52fa826d4c2 100644
--- a/code/game/objects/effects/semirandom_mobs_vr.dm
+++ b/code/game/objects/effects/semirandom_mobs_vr.dm
@@ -1018,6 +1018,7 @@ var/global/list/semirandom_mob_spawner_decisions = list()
overwrite_hostility = 1
mob_hostile = 0
mob_retaliate = 0
+ mob_ghostjoin = 25 //25% chance to be ghost joinable
/obj/random/mob/semirandom_mob_spawner/vore/passive/b
mob_faction = "pasvoreb"
@@ -1030,6 +1031,7 @@ var/global/list/semirandom_mob_spawner_decisions = list()
overwrite_hostility = 1
mob_hostile = 0
mob_retaliate = 1
+ mob_ghostjoin = 25 //25% chance to be ghost joinable
/obj/random/mob/semirandom_mob_spawner/vore/retaliate/b
mob_faction = "retvoreb"
diff --git a/code/game/objects/effects/temporary_visuals/temporary_visual.dm b/code/game/objects/effects/temporary_visuals/temporary_visual.dm
index 84e8e1c031c..5bbb7a19f1d 100644
--- a/code/game/objects/effects/temporary_visuals/temporary_visual.dm
+++ b/code/game/objects/effects/temporary_visuals/temporary_visual.dm
@@ -13,7 +13,7 @@
. = ..()
if(randomdir)
dir = pick(list(NORTH, SOUTH, EAST, WEST))
- timerid = QDEL_IN(src, duration)
+ timerid = QDEL_IN_STOPPABLE(src, duration)
/obj/effect/temp_visual/Destroy()
. = ..()
@@ -35,4 +35,3 @@
if(set_dir)
dir = set_dir
. = ..()
-
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 609822a7562..83844f79958 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -703,7 +703,14 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
//Looking through a scope or binoculars should /not/ improve your periphereal vision. Still, increase viewsize a tiny bit so that sniping isn't as restricted to NSEW
/obj/item/var/ignore_visor_zoom_restriction = FALSE
-/obj/item/proc/zoom(var/tileoffset = 14,var/viewsize = 9) //tileoffset is client view offset in the direction the user is facing. viewsize is how far out this thing zooms. 7 is normal view
+/obj/item/proc/zoom(var/mob/living/M, var/tileoffset = 14,var/viewsize = 9) //tileoffset is client view offset in the direction the user is facing. viewsize is how far out this thing zooms. 7 is normal view
+
+ if(isliving(usr)) //Always prefer usr if set
+ M = usr
+
+ if(!isliving(M))
+ return 0
+
var/devicename
@@ -714,25 +721,26 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
var/cannotzoom
- if((usr.stat && !zoom) || !(istype(usr,/mob/living/carbon/human)))
- to_chat(usr, "You are unable to focus through the [devicename].")
+ if((M.stat && !zoom) || !(istype(M,/mob/living/carbon/human)))
+ to_chat(M, "You are unable to focus through the [devicename].")
cannotzoom = 1
- else if(!zoom && (global_hud.darkMask[1] in usr.client.screen))
- to_chat(usr, "Your visor gets in the way of looking through the [devicename].")
+ else if(!zoom && (global_hud.darkMask[1] in M.client.screen))
+ to_chat(M, "Your visor gets in the way of looking through the [devicename].")
cannotzoom = 1
- else if(!zoom && usr.get_active_hand() != src)
- to_chat(usr, "You are too distracted to look through the [devicename], perhaps if it was in your active hand this might work better.")
+ else if(!zoom && M.get_active_hand() != src)
+ to_chat(M, "You are too distracted to look through the [devicename], perhaps if it was in your active hand this might work better.")
cannotzoom = 1
//We checked above if they are a human and returned already if they weren't.
- var/mob/living/carbon/human/H = usr
+ var/mob/living/carbon/human/H = M
if(!zoom && !cannotzoom)
if(H.hud_used.hud_shown)
H.toggle_zoom_hud() // If the user has already limited their HUD this avoids them having a HUD when they zoom in
H.set_viewsize(viewsize)
zoom = 1
- GLOB.moved_event.register(H, src, PROC_REF(zoom))
+ H.AddComponent(/datum/component/recursive_move)
+ RegisterSignal(H, COMSIG_OBSERVER_MOVED, PROC_REF(zoom), override = TRUE)
var/tilesize = 32
var/viewoffset = tilesize * tileoffset
@@ -751,7 +759,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
H.client.pixel_x = -viewoffset
H.client.pixel_y = 0
- H.visible_message("[usr] peers through the [zoomdevicename ? "[zoomdevicename] of the [src.name]" : "[src.name]"].")
+ H.visible_message("[M] peers through the [zoomdevicename ? "[zoomdevicename] of the [src.name]" : "[src.name]"].")
if(!ignore_visor_zoom_restriction)
H.looking_elsewhere = TRUE
H.handle_vision()
@@ -761,7 +769,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
if(!H.hud_used.hud_shown)
H.toggle_zoom_hud()
zoom = 0
- GLOB.moved_event.unregister(H, src, PROC_REF(zoom))
+ UnregisterSignal(H, COMSIG_OBSERVER_MOVED)
H.client.pixel_x = 0
H.client.pixel_y = 0
@@ -769,7 +777,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
H.handle_vision()
if(!cannotzoom)
- usr.visible_message("[zoomdevicename ? "[usr] looks up from the [src.name]" : "[usr] lowers the [src.name]"].")
+ M.visible_message("[zoomdevicename ? "[M] looks up from the [src.name]" : "[M] lowers the [src.name]"].")
return
diff --git a/code/game/objects/items/devices/ai_detector.dm b/code/game/objects/items/devices/ai_detector.dm
index 7bb92bf2168..d5ca5aaa521 100644
--- a/code/game/objects/items/devices/ai_detector.dm
+++ b/code/game/objects/items/devices/ai_detector.dm
@@ -94,22 +94,22 @@
if(new_state != old_state)
switch(new_state)
if(PROXIMITY_OFF_CAMERANET)
- to_chat(carrier, "\icon[src][bicon(src)] Now outside of camera network.")
+ to_chat(carrier, "[icon2html(src, carrier.client)] Now outside of camera network.")
carrier << 'sound/machines/defib_failed.ogg'
if(PROXIMITY_NONE)
- to_chat(carrier, "\icon[src][bicon(src)] Now within camera network, AI and cameras unfocused.")
+ to_chat(carrier, "[icon2html(src, carrier.client)] Now within camera network, AI and cameras unfocused.")
carrier << 'sound/machines/defib_safetyOff.ogg'
if(PROXIMITY_NEAR)
- to_chat(carrier, "\icon[src][bicon(src)] Warning: AI focus at nearby location.")
+ to_chat(carrier, "[icon2html(src, carrier.client)] Warning: AI focus at nearby location.")
carrier << 'sound/machines/defib_SafetyOn.ogg'
if(PROXIMITY_ON_SCREEN)
- to_chat(carrier, "\icon[src][bicon(src)] Alert: AI or camera focused at current location!")
+ to_chat(carrier, "[icon2html(src, carrier.client)] Alert: AI or camera focused at current location!")
carrier <<'sound/machines/defib_ready.ogg'
if(PROXIMITY_TRACKING)
- to_chat(carrier, "\icon[src][bicon(src)] Danger: AI is actively tracking you!")
+ to_chat(carrier, "[icon2html(src, carrier.client)] Danger: AI is actively tracking you!")
carrier << 'sound/machines/defib_success.ogg'
if(PROXIMITY_TRACKING_FAIL)
- to_chat(carrier, "\icon[src][bicon(src)] Danger: AI is attempting to actively track you, but you are outside of the camera network!")
+ to_chat(carrier, "[icon2html(src, carrier.client)] Danger: AI is attempting to actively track you, but you are outside of the camera network!")
carrier <<'sound/machines/defib_ready.ogg'
@@ -118,4 +118,4 @@
#undef PROXIMITY_NEAR
#undef PROXIMITY_ON_SCREEN
#undef PROXIMITY_TRACKING
-#undef PROXIMITY_TRACKING_FAIL
\ No newline at end of file
+#undef PROXIMITY_TRACKING_FAIL
diff --git a/code/game/objects/items/devices/communicator/UI_tgui.dm b/code/game/objects/items/devices/communicator/UI_tgui.dm
index 2f4e73c5eb5..fb59fd2a323 100644
--- a/code/game/objects/items/devices/communicator/UI_tgui.dm
+++ b/code/game/objects/items/devices/communicator/UI_tgui.dm
@@ -382,7 +382,7 @@
im_list += list(list("address" = exonet.address, "to_address" = their_address, "im" = text))
log_pda("(COMM: [src]) sent \"[text]\" to [exonet.get_atom_from_address(their_address)]", usr)
var/obj/item/device/communicator/comm = exonet.get_atom_from_address(their_address)
- to_chat(usr, "\icon[src][bicon(src)] Sent message to [istype(comm, /obj/item/device/communicator) ? comm.owner : comm.name], \"[text]\" (Reply)")
+ to_chat(usr, "[icon2html(src, usr.client)] Sent message to [istype(comm, /obj/item/device/communicator) ? comm.owner : comm.name], \"[text]\" (Reply)")
for(var/mob/M in player_list)
if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
diff --git a/code/game/objects/items/devices/communicator/communicator.dm b/code/game/objects/items/devices/communicator/communicator.dm
index d4fb3ff04ab..caece035daf 100644
--- a/code/game/objects/items/devices/communicator/communicator.dm
+++ b/code/game/objects/items/devices/communicator/communicator.dm
@@ -302,7 +302,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
/obj/item/device/communicator/Destroy()
for(var/mob/living/voice/voice in contents)
voice_mobs.Remove(voice)
- to_chat(voice, "\icon[src][bicon(src)] Connection timed out with remote host.")
+ to_chat(voice, "[icon2html(src, voice.client)] Connection timed out with remote host.")
qdel(voice)
close_connection(reason = "Connection timed out")
diff --git a/code/game/objects/items/devices/communicator/messaging.dm b/code/game/objects/items/devices/communicator/messaging.dm
index 2f9249dd5dc..84c930479e6 100644
--- a/code/game/objects/items/devices/communicator/messaging.dm
+++ b/code/game/objects/items/devices/communicator/messaging.dm
@@ -34,7 +34,7 @@
if(src in comm.voice_invites)
comm.open_connection(src)
return
- to_chat(src, "\icon[origin_atom][bicon(origin_atom)] Receiving communicator request from [origin_atom]. To answer, use the Call Communicator \
+ to_chat(src, "[icon2html(origin_atom,src.client)] Receiving communicator request from [origin_atom]. To answer, use the Call Communicator \
verb, and select that name to answer the call.")
src << 'sound/machines/defib_SafetyOn.ogg'
comm.voice_invites |= src
@@ -44,7 +44,7 @@
random = random / 10
exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
if(message == "text")
- to_chat(src, "\icon[origin_atom][bicon(origin_atom)] Received text message from [origin_atom]: \"[text]\"")
+ to_chat(src, "[icon2html(origin_atom,src.client)] Received text message from [origin_atom]: \"[text]\"")
src << 'sound/machines/defib_safetyOff.ogg'
exonet_messages.Add("From [origin_atom]: [text]")
return
@@ -84,7 +84,7 @@
playsound(src, S, 50, 1)
for (var/mob/O in hearers(2, loc))
- O.show_message(text("\icon[src][bicon(src)] *[ttone]*"))
+ O.show_message(text("[icon2html(src,O.client)] *[ttone]*"))
alert_called = 1
update_icon()
@@ -95,7 +95,7 @@
L = loc
if(L)
- to_chat(L, "\icon[src][bicon(src)] Message from [who]: \"[text]\" (Reply)")
+ to_chat(L, "[icon2html(src,L.client)] Message from [who]: \"[text]\" (Reply)")
// This is the only Topic the communicators really uses
/obj/item/device/communicator/Topic(href, href_list)
@@ -108,7 +108,7 @@
exonet.send_message(comm.exonet.address, "text", message)
im_list += list(list("address" = exonet.address, "to_address" = comm.exonet.address, "im" = message))
log_pda("(COMM: [src]) sent \"[message]\" to [exonet.get_atom_from_address(comm.exonet.address)]", usr)
- to_chat(usr, "\icon[src][bicon(src)] Sent message to [istype(comm, /obj/item/device/communicator) ? comm.owner : comm.name], \"[message]\" (Reply)")
+ to_chat(usr, "[icon2html(src,usr.client)] Sent message to [istype(comm, /obj/item/device/communicator) ? comm.owner : comm.name], \"[message]\" (Reply)")
// Verb: text_communicator()
// Parameters: None
diff --git a/code/game/objects/items/devices/communicator/phone.dm b/code/game/objects/items/devices/communicator/phone.dm
index bbf91d1ead1..76ec48aa5b4 100644
--- a/code/game/objects/items/devices/communicator/phone.dm
+++ b/code/game/objects/items/devices/communicator/phone.dm
@@ -39,15 +39,15 @@
comm.voice_requests.Remove(src)
if(user)
- comm.visible_message("\icon[src][bicon(src)] Connecting to [src].")
- to_chat(user, "\icon[src][bicon(src)] Attempting to call [comm].")
+ comm.visible_message("[icon2html(src,viewers(src))] Connecting to [src].")
+ to_chat(user, "[icon2html(src,user.client)] Attempting to call [comm].")
sleep(10)
- to_chat(user, "\icon[src][bicon(src)] Dialing internally from [station_name()], [system_name()].")
+ to_chat(user, "[icon2html(src,user.client)] Dialing internally from [station_name()], [system_name()].")
sleep(20) //If they don't have an exonet something is very wrong and we want a runtime.
- to_chat(user, "\icon[src][bicon(src)] Connection re-routed to [comm] at [comm.exonet.address].")
+ to_chat(user, "[icon2html(src,user.client)] Connection re-routed to [comm] at [comm.exonet.address].")
sleep(40)
- to_chat(user, "\icon[src][bicon(src)] Connection to [comm] at [comm.exonet.address] established.")
- comm.visible_message("\icon[src][bicon(src)] Connection to [src] at [exonet.address] established.")
+ to_chat(user, "[icon2html(src,user.client)] Connection to [comm] at [comm.exonet.address] established.")
+ comm.visible_message("[icon2html(src,viewers(src))] Connection to [src] at [exonet.address] established.")
sleep(20)
src.add_communicating(comm)
@@ -86,28 +86,28 @@
//Now for some connection fluff.
if(user)
- to_chat(user, "\icon[src][bicon(src)] Connecting to [candidate].")
- to_chat(new_voice, "\icon[src][bicon(src)] Attempting to call [src].")
+ to_chat(user, "[icon2html(src,user.client)] Connecting to [candidate].")
+ to_chat(new_voice, "[icon2html(src,new_voice.client)] Attempting to call [src].")
sleep(10)
- to_chat(new_voice, "\icon[src][bicon(src)] Dialing to [station_name()], Kara Subsystem, [system_name()].")
+ to_chat(new_voice, "[icon2html(src,new_voice.client)] Dialing to [station_name()], Kara Subsystem, [system_name()].")
sleep(20)
- to_chat(new_voice, "\icon[src][bicon(src)] Connecting to [station_name()] telecommunications array.")
+ to_chat(new_voice, "[icon2html(src,new_voice.client)] Connecting to [station_name()] telecommunications array.")
sleep(40)
- to_chat(new_voice, "\icon[src][bicon(src)] Connection to [station_name()] telecommunications array established. Redirecting signal to [src].")
+ to_chat(new_voice, "[icon2html(src,new_voice.client)] Connection to [station_name()] telecommunications array established. Redirecting signal to [src].")
sleep(20)
//We're connected, no need to hide everything.
new_voice.client.screen.Remove(blackness)
qdel(blackness)
- to_chat(new_voice, "\icon[src][bicon(src)] Connection to [src] established.")
+ to_chat(new_voice, "[icon2html(src,new_voice.client)] Connection to [src] established.")
to_chat(new_voice, "To talk to the person on the other end of the call, just talk normally.")
to_chat(new_voice, "If you want to end the call, use the 'Hang Up' verb. The other person can also hang up at any time.")
to_chat(new_voice, "Remember, your character does not know anything you've learned from observing!")
if(new_voice.mind)
new_voice.mind.assigned_role = "Disembodied Voice"
if(user)
- to_chat(user, "\icon[src][bicon(src)] Your communicator is now connected to [candidate]'s communicator.")
+ to_chat(user, "[icon2html(src,new_voice.client)] Your communicator is now connected to [candidate]'s communicator.")
// Proc: close_connection()
// Parameters: 3 (user - the user who initiated the disconnect, target - the mob or device being disconnected, reason - string shown when disconnected)
@@ -120,8 +120,8 @@
for(var/mob/living/voice/voice in voice_mobs) //Handle ghost-callers
if(target && voice != target) //If no target is inputted, it deletes all of them.
continue
- to_chat(voice, "\icon[src][bicon(src)] [reason].")
- visible_message("\icon[src][bicon(src)] [reason].")
+ to_chat(voice, "[icon2html(src,voice.client)] [reason].")
+ visible_message("[icon2html(src,viewers(src))] [reason].")
voice_mobs.Remove(voice)
qdel(voice)
update_icon()
@@ -131,8 +131,8 @@
continue
src.del_communicating(comm)
comm.del_communicating(src)
- comm.visible_message("\icon[src][bicon(src)] [reason].")
- visible_message("\icon[src][bicon(src)] [reason].")
+ comm.visible_message("[icon2html(src,viewers(src))] [reason].")
+ visible_message("[icon2html(src,viewers(src))] [reason].")
if(comm.camera && video_source == comm.camera) //We hung up on the person on video
end_video()
if(camera && comm.video_source == camera) //We hung up on them while they were watching us
@@ -163,7 +163,7 @@
if(ringer)
playsound(src, 'sound/machines/twobeep.ogg', 50, 1)
for (var/mob/O in hearers(2, loc))
- O.show_message(text("\icon[src][bicon(src)] *beep*"))
+ O.show_message(text("[icon2html(src,O.client)] *beep*"))
alert_called = 1
update_icon()
@@ -174,7 +174,7 @@
L = loc
if(L)
- to_chat(L, "\icon[src][bicon(src)] Communications request from [who].")
+ to_chat(L, "[icon2html(src,L.client)] Communications request from [who].")
// Proc: del_request()
// Parameters: 1 (candidate - the ghost or communicator to be declined)
@@ -197,13 +197,13 @@
us = loc
if(us)
- to_chat(us, "\icon[src][bicon(src)] Declined request.")
+ to_chat(us, "[icon2html(src,us.client)] Declined request.")
// Proc: see_emote()
// Parameters: 2 (M - the mob the emote originated from, text - the emote's contents)
// Description: Relays the emote to all linked communicators.
/obj/item/device/communicator/see_emote(mob/living/M, text)
- var/rendered = "\icon[src][bicon(src)] [text]"
+
for(var/obj/item/device/communicator/comm in communicating)
var/turf/T = get_turf(comm)
if(!T) return
@@ -216,7 +216,7 @@
var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0) //Range of 3 since it's a tiny video display
mobs_to_relay = in_range["mobs"]
//VOREStation Edit End
-
+ var/rendered = "[icon2html(src,mobs_to_relay)] [text]"
for(var/mob/mob in mobs_to_relay) //We can't use visible_message(), or else we will get an infinite loop if two communicators hear each other.
var/dst = get_dist(get_turf(mob),get_turf(comm))
if(dst <= video_range)
@@ -250,20 +250,20 @@
var/message = combined["formatted"]
var/name_used = M.GetVoice()
var/rendered = null
- rendered = "\icon[src][bicon(src)] [name_used] [message]"
+ rendered = "[icon2html(src,mobs_to_relay)] [name_used] [message]"
mob.show_message(rendered, 2)
// Proc: show_message()
// Parameters: 4 (msg - the message, type - number to determine if message is visible or audible, alt - unknown, alt_type - unknown)
// Description: Relays the message to all linked communicators.
/obj/item/device/communicator/show_message(msg, type, alt, alt_type)
- var/rendered = "\icon[src][bicon(src)] [msg]"
+
for(var/obj/item/device/communicator/comm in communicating)
var/turf/T = get_turf(comm)
if(!T) return
var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
var/list/mobs_to_relay = in_range["mobs"]
-
+ var/rendered = "[icon2html(src, mobs_to_relay)] [msg]"
for(var/mob/mob in mobs_to_relay)
mob.show_message(rendered)
..()
@@ -339,28 +339,29 @@
return
if(!(src in comm.communicating) || !comm.camera) //You called someone with a broken communicator or one that's fake or yourself or something
- to_chat(user, "\icon[src][bicon(src)]ERROR: Video failed. Either bandwidth is too low, or the other communicator is malfunctioning.")
+ to_chat(user, "[icon2html(src, user.client)]ERROR: Video failed. Either bandwidth is too low, or the other communicator is malfunctioning.")
return
- to_chat(user, "\icon[src][bicon(src)] Attempting to start video over existing call.")
+ to_chat(user, "[icon2html(src, user.client)] Attempting to start video over existing call.")
sleep(30)
- to_chat(user, "\icon[src][bicon(src)] Please wait...")
+ to_chat(user, "[icon2html(src, user.client)] Please wait...")
video_source = comm.camera
- comm.visible_message("\icon[src][bicon(src)] New video connection from [comm].")
+ comm.visible_message("[icon2html(src,viewers(src))] New video connection from [comm].")
update_active_camera_screen()
- GLOB.moved_event.register(video_source, src, PROC_REF(update_active_camera_screen))
+ RegisterSignal(video_source, COMSIG_OBSERVER_MOVED, PROC_REF(update_active_camera_screen))
+ video_source.AddComponent(/datum/component/recursive_move)
update_icon()
// Proc: end_video()
// Parameters: reason - the text reason to print for why it ended
// Description: Ends the video call by clearing video_source
/obj/item/device/communicator/proc/end_video(var/reason)
- GLOB.moved_event.unregister(video_source, src, PROC_REF(update_active_camera_screen))
+ UnregisterSignal(video_source, COMSIG_OBSERVER_MOVED)
show_static()
video_source = null
- . = "\icon[src][bicon(src)] [reason ? reason : "Video session ended"]."
+ . = "[bicon(src)] [reason ? reason : "Video session ended"]."
visible_message(.)
update_icon()
diff --git a/code/game/objects/items/devices/geiger.dm b/code/game/objects/items/devices/geiger.dm
index d127fe9404f..d12833ba6f2 100644
--- a/code/game/objects/items/devices/geiger.dm
+++ b/code/game/objects/items/devices/geiger.dm
@@ -65,7 +65,7 @@
STOP_PROCESSING(SSobj, src)
update_icon()
update_sound()
- to_chat(user, "\icon[src][bicon(src)] You switch [scanning ? "on" : "off"] \the [src].")
+ to_chat(user, "[icon2html(src, user.client)] You switch [scanning ? "on" : "off"] \the [src].")
/obj/item/device/geiger/update_icon()
if(!scanning)
@@ -120,7 +120,7 @@
scanning = !scanning
update_icon()
update_sound()
- to_chat(user, "\icon[src] You switch [scanning ? "on" : "off"] \the [src].")
+ to_chat(user, "[icon2html(src, user.client)] You switch [scanning ? "on" : "off"] \the [src].")
/obj/item/device/geiger/wall/update_icon()
if(!scanning)
@@ -165,4 +165,4 @@
/obj/item/device/geiger/wall/west
pixel_x = -28
- dir = WEST
\ No newline at end of file
+ dir = WEST
diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm
index 1861694fae9..df2ce116b16 100644
--- a/code/game/objects/items/devices/gps.dm
+++ b/code/game/objects/items/devices/gps.dm
@@ -38,15 +38,16 @@ var/list/GPS_list = list()
/obj/item/device/gps/proc/update_holder()
if(holder && loc != holder)
- GLOB.moved_event.unregister(holder, src)
- GLOB.dir_set_event.unregister(holder, src)
+ UnregisterSignal(holder, COMSIG_OBSERVER_MOVED)
+ //GLOB.dir_set_event.unregister(holder, src)
holder.client?.screen -= compass
holder = null
if(istype(loc, /mob))
holder = loc
- GLOB.moved_event.register(holder, src, PROC_REF(update_compass))
- GLOB.dir_set_event.register(holder, src, PROC_REF(update_compass))
+ RegisterSignal(holder, COMSIG_OBSERVER_MOVED, PROC_REF(update_compass), override = TRUE)
+ holder.AddComponent(/datum/component/recursive_move)
+ //GLOB.dir_set_event.register(holder, src, PROC_REF(update_compass))
if(holder && tracking)
if(!is_in_processing_list)
diff --git a/code/game/objects/items/devices/hacktool.dm b/code/game/objects/items/devices/hacktool.dm
index 38f1c2afdf6..f27ec959859 100644
--- a/code/game/objects/items/devices/hacktool.dm
+++ b/code/game/objects/items/devices/hacktool.dm
@@ -71,12 +71,12 @@
to_chat(user, "You are already hacking!")
return 0
if(!is_type_in_list(target, supported_types))
- to_chat(user, "\icon[src][bicon(src)] Unable to hack this target, invalid target type.")
+ to_chat(user, "[icon2html(src, user.client)] Unable to hack this target, invalid target type.")
return 0
var/obj/machinery/door/airlock/D = target
if(D.security_level > max_level)
- to_chat(user, "\icon[src][bicon(src)] Target's electronic security is too complex.")
+ to_chat(user, "[icon2html(src, user.client)] Target's electronic security is too complex.")
return 0
var/found = known_targets.Find(D)
diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm
index dc71c0cafc2..8d322493ea6 100644
--- a/code/game/objects/items/devices/radio/intercom.dm
+++ b/code/game/objects/items/devices/radio/intercom.dm
@@ -20,13 +20,15 @@
. = ..()
var/area/A = get_area(src)
if(A)
- GLOB.apc_event.register(A, src, /atom/proc/update_icon)
+ RegisterSignal(A, COMSIG_OBSERVER_APC, /atom/proc/update_icon)
update_icon()
/obj/item/device/radio/intercom/Destroy()
var/area/A = get_area(src)
if(A)
- GLOB.apc_event.unregister(A, src, /atom/proc/update_icon)
+ UnregisterSignal(A, COMSIG_OBSERVER_APC)
+ if(circuit)
+ QDEL_NULL(circuit)
return ..()
/obj/item/device/radio/intercom/custom
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 616ce62b456..26af181ae92 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -496,7 +496,7 @@ GLOBAL_DATUM(autospeaker, /mob/living/silicon/ai/announcer)
distance = 99
else
distance = jamming["distance"]
- to_chat(M, "\icon[src][bicon(src)] You hear the [distance <= 2 ? "loud hiss" : "soft hiss"] of static.")
+ to_chat(M, "[icon2html(src, M.client)] You hear the [distance <= 2 ? "loud hiss" : "soft hiss"] of static.")
return FALSE
// First, we want to generate a new radio signal
diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm
index 55b744f0dc3..d2fa720e6a1 100644
--- a/code/game/objects/items/devices/text_to_speech.dm
+++ b/code/game/objects/items/devices/text_to_speech.dm
@@ -24,7 +24,7 @@
var/message = sanitize(tgui_input_text(user,"Choose a message to relay to those around you."))
if(message)
- audible_message("\icon[src][bicon(src)] \The [src.name] states, \"[message]\"", runemessage = "synthesized speech")
+ audible_message("[icon2html(src, user.client)] \The [src.name] states, \"[message]\"", runemessage = "synthesized speech")
if(ismob(loc))
loc.runechat_message("\[TTS Voice\] [message]")
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index c7563b4412f..30ff96b052a 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -36,6 +36,8 @@
/obj/item/borg/sight/xray
name = "\proper x-ray vision"
sight_mode = BORGXRAY
+ icon_state = "night"
+ icon = 'icons/inventory/eyes/item.dmi'
/obj/item/borg/sight/thermal
@@ -57,6 +59,12 @@
icon_state = "material"
icon = 'icons/inventory/eyes/item.dmi'
+/obj/item/borg/sight/anomalous
+ name = "\proper anomaly vision"
+ sight_mode = BORGANOMALOUS
+ icon_state = "denight"
+ icon = 'icons/inventory/eyes/item.dmi'
+
/obj/item/borg/sight/hud
name = "hud"
var/obj/item/clothing/glasses/hud/hud = null
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 34892beab26..fe41bc658ff 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -18,6 +18,16 @@
return 1
return 0
+/obj/item/borg/upgrade/proc/generic_error(var/mob/living/silicon/robot/R, var/obj/item/borg/type)
+ type = lowertext(initial(type.name))
+ to_chat(R, "Upgrade mounting error! No suitable hardpoint for \the \"[type]\" detected!")
+ to_chat(usr, "There's no mounting point for \the \"[type]\" module!")
+
+/obj/item/borg/upgrade/proc/software_error(var/mob/living/silicon/robot/R, var/obj/item/borg/type)
+ type = lowertext(initial(type.name))
+ to_chat(R, "Upgrade installation error! Incompatibility with \the \"[type]\" detected!")
+ to_chat(usr, "\The \"[type]\" upgrade is not compatibile!")
+
/* ######################################################################################################
# Utility section. All reusable upgrades without lasting effects, like renaming, reset, etc. go here.#
######################################################################################################*/
@@ -109,7 +119,8 @@
return 0
R.verbs += /mob/living/silicon/robot/proc/toggle_vtec
- R.speed = -1
+ R.vtec_active = TRUE
+ R.hud_used.toggle_vtec_control()
return 1
/obj/item/borg/upgrade/basic/sizeshift
@@ -251,8 +262,7 @@
if(..()) return 0
if(R.has_advanced_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
R.module.modules += new/obj/item/weapon/tank/jetpack/carbondioxide(R.module)
@@ -271,8 +281,7 @@
if(..()) return 0
if(R.has_advanced_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
R.module.modules += new/obj/item/device/healthanalyzer/advanced(R.module)
@@ -290,8 +299,7 @@
if(..()) return 0
if(R.has_advanced_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
R.module.modules += new/obj/item/weapon/gun/energy/sizegun/mounted(R.module)
@@ -314,8 +322,7 @@
if(..()) return 0
if(!R.supports_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
var/obj/T = R.has_upgrade_module(/obj/item/device/dogborg/sleeper)
@@ -349,8 +356,7 @@
if(..()) return 0
if(!R.supports_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
var/obj/T = R.has_upgrade_module(/obj/item/weapon/gun/energy/taser/mounted/cyborg)
@@ -380,13 +386,11 @@
if(..()) return 0
if(!R.supports_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
if(R.has_restricted_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
R.module.modules += new/obj/item/weapon/storage/part_replacer/adv(R.module)
@@ -405,13 +409,11 @@
if(..()) return 0
if(!R.supports_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
if(R.has_restricted_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
R.module.modules += new/obj/item/weapon/pickaxe/diamonddrill(R.module)
@@ -430,13 +432,11 @@
if(..()) return 0
if(!R.supports_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
if(R.has_restricted_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
R.module.modules += new/obj/item/weapon/gun/energy/kinetic_accelerator/cyborg(R.module)
@@ -461,9 +461,98 @@
if(..()) return 0
if(R.has_no_prod_upgrade(type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
+ generic_error(R, type)
return 0
R.module.modules += new/obj/item/weapon/gun/projectile/cyborgtoy(R.module)
return 1
+
+/obj/item/borg/upgrade/no_prod/vision_xray
+ name = "Robot x-ray vision module"
+ desc = "Vision alterantion software to add x-ray sight capabilities."
+ icon_state = "cyborg_upgrade5"
+ item_state = "cyborg_upgrade"
+ require_module = 1
+ hidden_from_scan = 1
+
+/obj/item/borg/upgrade/no_prod/vision_xray/action(var/mob/living/silicon/robot/R)
+ if(..()) return 0
+
+ if(R.has_no_prod_upgrade(type))
+ software_error(R, type)
+ return 0
+
+ R.module.modules += new/obj/item/borg/sight/xray(R.module)
+ return 1
+
+/obj/item/borg/upgrade/no_prod/vision_thermal
+ name = "Robot thermal vision module"
+ desc = "Vision alterantion software to add thermal sight capabilities."
+ icon_state = "cyborg_upgrade5"
+ item_state = "cyborg_upgrade"
+ require_module = 1
+ hidden_from_scan = 1
+
+/obj/item/borg/upgrade/no_prod/vision_thermal/action(var/mob/living/silicon/robot/R)
+ if(..()) return 0
+
+ if(R.has_no_prod_upgrade(type))
+ software_error(R, type)
+ return 0
+
+ R.module.modules += new/obj/item/borg/sight/thermal(R.module)
+ return 1
+
+/obj/item/borg/upgrade/no_prod/vision_meson
+ name = "Robot meson vision module"
+ desc = "Vision alterantion software to add meson sight capabilities."
+ icon_state = "cyborg_upgrade5"
+ item_state = "cyborg_upgrade"
+ require_module = 1
+ hidden_from_scan = 1
+
+/obj/item/borg/upgrade/no_prod/vision_meson/action(var/mob/living/silicon/robot/R)
+ if(..()) return 0
+
+ if(R.has_no_prod_upgrade(type))
+ software_error(R, type)
+ return 0
+
+ R.module.modules += new/obj/item/borg/sight/meson(R.module)
+ return 1
+
+/obj/item/borg/upgrade/no_prod/vision_material
+ name = "Robot material vision module"
+ desc = "Vision alterantion software to add material sight capabilities."
+ icon_state = "cyborg_upgrade5"
+ item_state = "cyborg_upgrade"
+ require_module = 1
+ hidden_from_scan = 1
+
+/obj/item/borg/upgrade/no_prod/vision_material/action(var/mob/living/silicon/robot/R)
+ if(..()) return 0
+
+ if(R.has_no_prod_upgrade(type))
+ software_error(R, type)
+ return 0
+
+ R.module.modules += new/obj/item/borg/sight/material(R.module)
+ return 1
+
+/obj/item/borg/upgrade/no_prod/vision_anomalous
+ name = "Robot anomalous vision module"
+ desc = "Vision alterantion software to add anomalous sight capabilities."
+ icon_state = "cyborg_upgrade5"
+ item_state = "cyborg_upgrade"
+ require_module = 1
+ hidden_from_scan = 1
+
+/obj/item/borg/upgrade/no_prod/vision_anomalous/action(var/mob/living/silicon/robot/R)
+ if(..()) return 0
+
+ if(R.has_no_prod_upgrade(type))
+ software_error(R, type)
+ return 0
+
+ R.module.modules += new/obj/item/borg/sight/anomalous(R.module)
+ return 1
diff --git a/code/game/objects/items/toys/toys.dm b/code/game/objects/items/toys/toys.dm
index 2331485fd17..9dadaff977c 100644
--- a/code/game/objects/items/toys/toys.dm
+++ b/code/game/objects/items/toys/toys.dm
@@ -716,7 +716,7 @@
if(stored_item && opened && !searching)
searching = TRUE
if(do_after(user, 10))
- to_chat(user, "You find \icon[stored_item] [stored_item] in [src]!")
+ to_chat(user, "You find [icon2html(stored_item, user.client)] [stored_item] in [src]!")
stored_item.forceMove(get_turf(src))
stored_item = null
searching = FALSE
@@ -813,7 +813,7 @@
if(stored_item && opened && !searching)
searching = TRUE
if(do_after(user, 10))
- to_chat(user, "You find \icon[stored_item] [stored_item] in [src]!")
+ to_chat(user, "You find [icon2html(stored_item, user.client)] [stored_item] in [src]!")
stored_item.forceMove(get_turf(src))
stored_item = null
searching = FALSE
@@ -1434,4 +1434,4 @@
/obj/structure/balloon/ghost
name = "giant ghost balloon"
desc = "Oh no, it's a ghost! Oh wait, it's just a balloon. Phew!"
- icon_state = "ghostballoon"
\ No newline at end of file
+ icon_state = "ghostballoon"
diff --git a/code/game/objects/items/toys/toys_vr.dm b/code/game/objects/items/toys/toys_vr.dm
index 12ce075114b..a5a08d16358 100644
--- a/code/game/objects/items/toys/toys_vr.dm
+++ b/code/game/objects/items/toys/toys_vr.dm
@@ -667,7 +667,7 @@
/obj/item/toy/minigibber/attackby(obj/O, mob/user, params)
if(istype(O,/obj/item/toy/figure) || istype(O,/obj/item/toy/character) && O.loc == user)
- to_chat(user, "You start feeding \the [O] \icon[O][bicon(O)] into \the [src]'s mini-input.")
+ to_chat(user, "You start feeding \the [O] [icon2html(O, user.client)] into \the [src]'s mini-input.")
if(do_after(user, 10, target = src))
if(O.loc != user)
to_chat(user, "\The [O] is too far away to feed into \the [src]!")
diff --git a/code/game/objects/items/weapons/capture_crystal.dm b/code/game/objects/items/weapons/capture_crystal.dm
index 450a2c6e39e..6d0be3819ca 100644
--- a/code/game/objects/items/weapons/capture_crystal.dm
+++ b/code/game/objects/items/weapons/capture_crystal.dm
@@ -399,9 +399,7 @@
active = TRUE
else //Shoot, it didn't work and now it's mad!!!
S.ai_holder.go_wake()
- S.ai_holder.target = user
- S.ai_holder.track_target_position()
- S.ai_holder.set_stance(STANCE_FIGHT)
+ S.ai_holder.give_target(user, urgent = TRUE)
user.visible_message("\The [src] bonks into \the [S], angering it!")
playsound(src, 'sound/effects/capture-crystal-negative.ogg', 75, 1, -1)
to_chat(user, "\The [src] clicks unsatisfyingly.")
@@ -844,7 +842,13 @@
list(/mob/living/simple_mob/vore/sheep),
list(/mob/living/simple_mob/vore/weretiger),
list(/mob/living/simple_mob/vore/alienanimals/skeleton),
- list(/mob/living/simple_mob/vore/alienanimals/dustjumper)
+ list(/mob/living/simple_mob/vore/alienanimals/dustjumper),
+ list(/mob/living/simple_mob/vore/cryptdrake),
+ list(/mob/living/simple_mob/vore/stalker),
+ list(/mob/living/simple_mob/vore/horse/kelpie),
+ list(/mob/living/simple_mob/vore/scrubble),
+ list(/mob/living/simple_mob/vore/sonadile),
+ list(/mob/living/simple_mob/vore/devil)
)
/obj/item/capture_crystal/random/Initialize()
diff --git a/code/game/objects/items/weapons/circuitboards/frame.dm b/code/game/objects/items/weapons/circuitboards/frame.dm
index c9bed28a5ea..15644652944 100644
--- a/code/game/objects/items/weapons/circuitboards/frame.dm
+++ b/code/game/objects/items/weapons/circuitboards/frame.dm
@@ -62,6 +62,13 @@
board_type = new /datum/frame/frame_types/intercom
matter = list(MAT_STEEL = 50, MAT_GLASS = 50)
+
+/obj/item/weapon/circuitboard/intercom/Destroy()
+ if(istype(loc, /obj/item/device/radio/intercom))
+ var/obj/item/device/radio/intercom/my_machine = loc
+ my_machine.circuit = null
+ . = ..()
+
/obj/item/weapon/circuitboard/keycard_auth
name = T_BOARD("keycard authenticator")
build_path = /obj/machinery/keycard_auth
diff --git a/code/game/objects/items/weapons/id cards/station_ids.dm b/code/game/objects/items/weapons/id cards/station_ids.dm
index f7b1f3b1972..fe35bdc4534 100644
--- a/code/game/objects/items/weapons/id cards/station_ids.dm
+++ b/code/game/objects/items/weapons/id cards/station_ids.dm
@@ -93,8 +93,8 @@
return data
/obj/item/weapon/card/id/attack_self(mob/user as mob)
- user.visible_message("\The [user] shows you: \icon[src][bicon(src)] [src.name]. The assignment on the card: [src.assignment]",\
- "You flash your ID card: \icon[src][bicon(src)] [src.name]. The assignment on the card: [src.assignment]")
+ user.visible_message("\The [user] shows you: [icon2html(src,viewers(src))] [src.name]. The assignment on the card: [src.assignment]",\
+ "You flash your ID card: [icon2html(src, user.client)] [src.name]. The assignment on the card: [src.assignment]")
src.add_fingerprint(user)
return
@@ -110,7 +110,7 @@
set category = "Object"
set src in usr
- to_chat(usr, "\icon[src][bicon(src)] [src.name]: The current assignment on the card is [src.assignment].")
+ to_chat(usr, "[icon2html(src, usr.client)] [src.name]: The current assignment on the card is [src.assignment].")
to_chat(usr, "The blood type on the card is [blood_type].")
to_chat(usr, "The DNA hash on the card is [dna_hash].")
to_chat(usr, "The fingerprint hash on the card is [fingerprint_hash].")
diff --git a/code/game/objects/items/weapons/paint.dm b/code/game/objects/items/weapons/paint.dm
index 3162718b7d8..d301828f326 100644
--- a/code/game/objects/items/weapons/paint.dm
+++ b/code/game/objects/items/weapons/paint.dm
@@ -26,8 +26,8 @@ var/global/list/cached_icons = list()
else
return ..()
-/obj/item/weapon/reagent_containers/glass/paint/New()
- ..()
+/obj/item/weapon/reagent_containers/glass/paint/Initialize()
+ .=..()
if(paint_type)
reagents.add_reagent("paint", volume, paint_type)
diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm
index a7137dded17..3d26e518a19 100644
--- a/code/game/objects/items/weapons/storage/bags.dm
+++ b/code/game/objects/items/weapons/storage/bags.dm
@@ -206,14 +206,16 @@
/obj/item/weapon/storage/bag/ore/equipped(mob/user)
..()
if(user.get_inventory_slot(src) == slot_wear_suit || slot_l_hand || slot_l_hand || slot_belt) //Basically every place they can go. Makes sure it doesn't unregister if moved to other slots.
- GLOB.moved_event.register(user, src, /obj/item/weapon/storage/bag/ore/proc/autoload, user)
+ user.AddComponent(/datum/component/recursive_move)
+ RegisterSignal(user, COMSIG_OBSERVER_MOVED, /obj/item/weapon/storage/bag/ore/proc/autoload, user, override = TRUE)
/obj/item/weapon/storage/bag/ore/dropped(mob/user)
..()
if(user.get_inventory_slot(src) == slot_wear_suit || slot_l_hand || slot_l_hand || slot_belt) //See above. This should really be a define.
- GLOB.moved_event.register(user, src, /obj/item/weapon/storage/bag/ore/proc/autoload, user)
+ user.AddComponent(/datum/component/recursive_move)
+ RegisterSignal(user, COMSIG_OBSERVER_MOVED, /obj/item/weapon/storage/bag/ore/proc/autoload, user, override = TRUE)
else
- GLOB.moved_event.unregister(user, src)
+ UnregisterSignal(user, COMSIG_OBSERVER_MOVED)
/obj/item/weapon/storage/bag/ore/proc/autoload(mob/user)
var/obj/item/weapon/ore/O = locate() in get_turf(src)
@@ -492,4 +494,4 @@
max_storage_space = ITEMSIZE_COST_NORMAL * 15
max_w_class = ITEMSIZE_NORMAL
w_class = ITEMSIZE_SMALL
- can_hold = list(/obj/item/weapon/forensics/swab,/obj/item/weapon/sample/print,/obj/item/weapon/sample/fibers,/obj/item/weapon/evidencebag)
\ No newline at end of file
+ can_hold = list(/obj/item/weapon/forensics/swab,/obj/item/weapon/sample/print,/obj/item/weapon/sample/fibers,/obj/item/weapon/evidencebag)
diff --git a/code/game/objects/items/weapons/syndie.dm b/code/game/objects/items/weapons/syndie.dm
index 5e519f8f4f0..a5cb000f715 100644
--- a/code/game/objects/items/weapons/syndie.dm
+++ b/code/game/objects/items/weapons/syndie.dm
@@ -50,7 +50,7 @@
icon_state = "c-4[size]_1"
playsound(src, 'sound/weapons/armbomb.ogg', 75, 1)
for(var/mob/O in hearers(src, null))
- O.show_message("\icon[src][bicon(src)] The [src.name] beeps! ")
+ O.show_message("[icon2html(src, O.client)] The [src.name] beeps! ")
sleep(50)
explosion(get_turf(src), devastate, heavy_impact, light_impact, flash_range)
for(var/dirn in cardinal) //This is to guarantee that C4 at least breaks down all immediately adjacent walls and doors.
diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm
index 8c176ecbbb3..c1cb09fecba 100644
--- a/code/game/objects/items/weapons/tanks/tanks.dm
+++ b/code/game/objects/items/weapons/tanks/tanks.dm
@@ -455,7 +455,7 @@ var/list/global/tank_gauge_cache = list()
return
T.assume_air(air_contents)
playsound(src, 'sound/weapons/Gunshot_shotgun.ogg', 20, 1)
- visible_message("\icon[src][bicon(src)] \The [src] flies apart!", "You hear a bang!")
+ visible_message("[icon2html(src,viewers(src))] \The [src] flies apart!", "You hear a bang!")
T.hotspot_expose(air_contents.temperature, 70, 1)
@@ -500,7 +500,7 @@ var/list/global/tank_gauge_cache = list()
T.assume_air(leaked_gas)
if(!leaking)
- visible_message("\icon[src][bicon(src)] \The [src] relief valve flips open with a hiss!", "You hear hissing.")
+ visible_message("[icon2html(src,viewers(src))] \The [src] relief valve flips open with a hiss!", "You hear hissing.")
playsound(src, 'sound/effects/spray.ogg', 10, 1, -3)
leaking = 1
#ifdef FIREDBG
diff --git a/code/game/objects/items/weapons/tools/combitool.dm b/code/game/objects/items/weapons/tools/combitool.dm
index 2f2ddbf4a5a..bfc4baeb858 100644
--- a/code/game/objects/items/weapons/tools/combitool.dm
+++ b/code/game/objects/items/weapons/tools/combitool.dm
@@ -29,7 +29,7 @@
if(loc == user && tools.len)
. += "It has the following fittings:"
for(var/obj/item/tool in tools)
- . += "\icon[tool][bicon(tool)] - [tool.name][tools[current_tool]==tool?" (selected)":""]")
+ . += "[icon2html(tool,)] - [tool.name][tools[current_tool]==tool?" (selected)":""]")
/obj/item/weapon/combitool/New()
..()
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 21b78b9a49e..11667d2f53e 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -57,7 +57,7 @@
/obj/CanUseTopic(var/mob/user, var/datum/tgui_state/state = GLOB.tgui_default_state)
if(user.CanUseObjTopic(src))
return ..()
- to_chat(user, "\icon[src][bicon(src)]Access Denied!")
+ to_chat(user, "[icon2html(src, user.client)]Access Denied!")
return STATUS_CLOSE
/mob/living/silicon/CanUseObjTopic(var/obj/O)
diff --git a/code/game/objects/random/mob.dm b/code/game/objects/random/mob.dm
index 82d828535ad..e00c10a6db9 100644
--- a/code/game/objects/random/mob.dm
+++ b/code/game/objects/random/mob.dm
@@ -15,6 +15,7 @@
var/mob_wander_distance = 3
var/mob_hostile = 0
var/mob_retaliate = 0
+ var/mob_ghostjoin = 0 //Should be a number between 0 and 100, dictates the probability of that mob being ghost joinable.
/obj/random/mob/item_to_spawn()
return pick(prob(10);/mob/living/simple_mob/animal/passive/lizard,
@@ -62,7 +63,9 @@
if(mob_faction)
M.faction = mob_faction
-
+ if(mob_ghostjoin)
+ if(prob(mob_ghostjoin))
+ M.ghostjoin = 1
/obj/random/mob/sif
name = "Random Sif Animal"
diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm
index e63debb8efe..8e5b177ab9c 100644
--- a/code/game/objects/structures/bedsheet_bin.dm
+++ b/code/game/objects/structures/bedsheet_bin.dm
@@ -181,11 +181,12 @@ LINEN BINS
. += "There are [amount] bed sheets in the bin."
/obj/structure/bedsheetbin/update_icon()
- switch(amount)
- if(0) icon_state = "linenbin-empty"
- if(1 to amount / 2) icon_state = "linenbin-half"
- else icon_state = "linenbin-full"
-
+ if(amount == 0)
+ icon_state = "linenbin-empty"
+ else if(amount <= (amount / 2))
+ icon_state = "linenbin-half"
+ else
+ icon_state = "linenbin-full"
/obj/structure/bedsheetbin/attackby(obj/item/I as obj, mob/user as mob)
if(istype(I, /obj/item/weapon/bedsheet))
diff --git a/code/game/objects/structures/catwalk.dm b/code/game/objects/structures/catwalk.dm
index ffed750917f..026aa59c845 100644
--- a/code/game/objects/structures/catwalk.dm
+++ b/code/game/objects/structures/catwalk.dm
@@ -64,10 +64,10 @@
/obj/structure/catwalk/ex_act(severity)
switch(severity)
if(1)
- new /obj/item/stack/rods(src.loc)
+ new /obj/item/stack/rods(src.loc, 2) //VOREstation Edit: Conservation of mass
qdel(src)
if(2)
- new /obj/item/stack/rods(src.loc)
+ new /obj/item/stack/rods(src.loc, 2) //VOREstation Edit: Conservation of mass
qdel(src)
/obj/structure/catwalk/attack_robot(var/mob/user)
@@ -77,11 +77,12 @@
/obj/structure/catwalk/proc/deconstruct(mob/user)
playsound(src, 'sound/items/Welder.ogg', 100, 1)
to_chat(user, "Slicing \the [src] joints ...")
- new /obj/item/stack/rods(src.loc)
- new /obj/item/stack/rods(src.loc)
//Lattice would delete itself, but let's save ourselves a new obj
- if(isspace(loc) || isopenspace(loc))
+ if(isopenspace(loc) && user.a_intent == I_HELP)
new /obj/structure/lattice/(src.loc)
+ new /obj/item/stack/rods(src.loc, 1)
+ else
+ new /obj/item/stack/rods(src.loc, 2)
if(plated_tile)
new plated_tile(src.loc)
qdel(src)
@@ -200,4 +201,4 @@
/obj/effect/catwalk_plated/techfloor
icon_state = "catwalk_techfloor"
tile = /obj/item/stack/tile/floor/techgrey
- platecolor = "#363f43"
\ No newline at end of file
+ platecolor = "#363f43"
diff --git a/code/game/objects/structures/flora/flora.dm b/code/game/objects/structures/flora/flora.dm
index fe69c9f30fc..d665ca5ab80 100644
--- a/code/game/objects/structures/flora/flora.dm
+++ b/code/game/objects/structures/flora/flora.dm
@@ -300,7 +300,7 @@
user.drop_from_inventory(I, src)
I.forceMove(src)
stored_item = I
- src.visible_message("\icon[src][bicon(src)] \icon[I][bicon(I)] [user] places [I] into [src].")
+ src.visible_message("[icon2html(src,viewers(src))] [icon2html(I,viewers(src))] [user] places [I] into [src].")
return
else
to_chat(user, "You refrain from putting things into the plant pot.")
@@ -311,7 +311,7 @@
to_chat(user, "You see nothing of interest in [src]...")
else
if(do_after(user, 10))
- to_chat(user, "You find \icon[stored_item][bicon(stored_item)] [stored_item] in [src]!")
+ to_chat(user, "You find [icon2html(stored_item, user.client)] [stored_item] in [src]!")
stored_item.forceMove(get_turf(src))
stored_item = null
..()
@@ -684,4 +684,3 @@
/obj/structure/flora/underwater/grass4
icon_state = "grass-4"
-
diff --git a/code/game/objects/structures/ghost_pods/event_vr.dm b/code/game/objects/structures/ghost_pods/event_vr.dm
index 95aac206ab1..6f378d92a07 100644
--- a/code/game/objects/structures/ghost_pods/event_vr.dm
+++ b/code/game/objects/structures/ghost_pods/event_vr.dm
@@ -46,6 +46,10 @@
"Frost Giant Spider" = /mob/living/simple_mob/animal/giant_spider/frost,
"Nurse Giant Spider" = /mob/living/simple_mob/animal/giant_spider/nurse/eggless,
"Giant Spider Queen" = /mob/living/simple_mob/animal/giant_spider/nurse/queen/eggless,
+ "Red Dragon" = /mob/living/simple_mob/vore/aggressive/dragon,
+ "Phoron Dragon" = /mob/living/simple_mob/vore/aggressive/dragon/virgo3b,
+ "Space Dragon" = /mob/living/simple_mob/vore/aggressive/dragon/space,
+ "Crypt Drake" = /mob/living/simple_mob/vore/cryptdrake,
"Weretiger" = /mob/living/simple_mob/vore/weretiger,
"Catslug" = /mob/living/simple_mob/vore/alienanimals/catslug,
"Squirrel" = /mob/living/simple_mob/vore/squirrel/big,
@@ -60,7 +64,13 @@
"Scel (Blue)" = /mob/living/simple_mob/vore/scel/blue,
"Scel (Purple)" = /mob/living/simple_mob/vore/scel/purple,
"Scel (Red)" = /mob/living/simple_mob/vore/scel/red,
- "Scel (Green)" = /mob/living/simple_mob/vore/scel/green
+ "Scel (Green)" = /mob/living/simple_mob/vore/scel/green,
+ "Cave Stalker" = /mob/living/simple_mob/vore/stalker,
+ "Kelpie" = /mob/living/simple_mob/vore/horse/kelpie,
+ "Scrubble" = /mob/living/simple_mob/vore/scrubble,
+ "Sonadile" = /mob/living/simple_mob/vore/sonadile,
+ "kururak" = /mob/living/simple_mob/animal/sif/kururak,
+ "Statue of Temptation" = /mob/living/simple_mob/vore/devil
)
/obj/structure/ghost_pod/ghost_activated/maintpred/create_occupant(var/mob/M)
@@ -150,3 +160,15 @@
/obj/structure/ghost_pod/ghost_activated/morphspawn/no_announce
announce_prob = 0
+
+/obj/structure/ghost_pod/ghost_activated/maintpred/redgate //For ghostpods placed in the redgate that aren't spawned via an event
+ name = "creature hole"
+ desc = "Looks like some creature dug is hiding in the redgate..."
+ announce_prob = 0
+ icon_state = "redgate_hole"
+ icon_state_opened = "redgate_hole"
+
+/obj/structure/ghost_pod/ghost_activated/maintpred/redgate/Initialize()
+ ..()
+ if(!(src in active_ghost_pods))
+ active_ghost_pods += src
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index d1fd8eb64df..b31b9580738 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -291,7 +291,7 @@
reinforcing = 0
/obj/structure/girder/proc/dismantle()
- girder_material.place_dismantled_product(get_turf(src))
+ girder_material.place_dismantled_product(get_turf(src), 2) //VOREstation Edit: Conservation of mass
qdel(src)
/obj/structure/girder/attack_hand(mob/user as mob)
diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm
index 8450a219b5f..370d494f357 100644
--- a/code/game/objects/structures/janicart.dm
+++ b/code/game/objects/structures/janicart.dm
@@ -119,9 +119,9 @@ GLOBAL_LIST_BOILERPLATE(all_janitorial_carts, /obj/structure/janitorialcart)
. = ..(user)
if(istype(mybucket))
var/contains = mybucket.reagents.total_volume
- . += "\icon[src][bicon(src)] The bucket contains [contains] unit\s of liquid!"
+ . += "[icon2html(src, user.client)] The bucket contains [contains] unit\s of liquid!"
else
- . += "\icon[src][bicon(src)] There is no bucket mounted on it!"
+ . += "[icon2html(src, user.client)] There is no bucket mounted on it!"
/obj/structure/janitorialcart/MouseDrop_T(atom/movable/O as mob|obj, mob/living/user as mob)
if (istype(O, /obj/structure/mopbucket) && !mybucket)
diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm
index 978b99ab3df..0ad35d794bd 100644
--- a/code/game/objects/structures/lattice.dm
+++ b/code/game/objects/structures/lattice.dm
@@ -64,20 +64,12 @@
if(WT.welding == 1)
if(WT.remove_fuel(0, user))
to_chat(user, "Slicing lattice joints ...")
- new /obj/item/stack/rods(src.loc)
+ new /obj/item/stack/rods(src.loc, 1) //VOREstation Edit: Return the same amount of rods used to build this.
qdel(src)
return
- if(istype(C, /obj/item/stack/rods))
- var/obj/item/stack/rods/R = C
- if(R.get_amount() < 2)
- to_chat(user, "You need at least two rods to form a catwalk here.")
- else
- to_chat(user, "You start connecting \the [R.name] to \the [src.name] ...")
- if(do_after(user, 5 SECONDS))
- R.use(2) //2023-02-27 bugfix to prevent rods being used without catwalk creation
- src.alpha = 0 // Note: I don't know why this is set, Eris did it, just trusting for now. ~Leshana
- new /obj/structure/catwalk(src.loc)
- qdel(src)
+ if(istype(C, /obj/item/stack/rods)) //VOREstation Edit: Modernizes upgrading lattices into catwalks.
+ upgrade(C, user)
+ //VOREstation Edit End
return
return
@@ -98,3 +90,11 @@
icon_state = "lattice[dir_sum]"
return
+
+//Vorestation Edit: Moves upgrading lattices to their own proc for other stuff to call. Also makes them instant.
+/obj/structure/lattice/proc/upgrade(obj/item/stack/rods/R, mob/user)
+ to_chat(user, "You start connecting \the [R.name] to \the [src.name] ...")
+ R.use(1)
+ src.alpha = 0 // Note: I don't know why this is set, Eris did it, just trusting for now. ~Leshana
+ new /obj/structure/catwalk(src.loc)
+ qdel(src)
diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm
index faa2351b6fb..426cc23ea41 100644
--- a/code/game/turfs/simulated/floor.dm
+++ b/code/game/turfs/simulated/floor.dm
@@ -85,7 +85,7 @@
if(!is_plating()) // Flooring -> Plating
swap_decals()
if(flooring.build_type && place_product)
- new flooring.build_type(src)
+ new flooring.build_type(src, flooring.build_cost) //VOREstation Edit: conservation of mass
var/newtype = flooring.get_plating_type()
if(newtype) // Has a custom plating type to become
set_flooring(get_flooring_data(newtype))
@@ -182,4 +182,4 @@
var/mob/living/livingUser = user
if(try_graffiti(livingUser, livingUser.get_active_hand()))
return
- . = ..()
\ No newline at end of file
+ . = ..()
diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm
index 52cedfdbfd5..96971a1bb20 100644
--- a/code/game/turfs/simulated/walls.dm
+++ b/code/game/turfs/simulated/walls.dm
@@ -197,9 +197,10 @@
else
material.place_dismantled_girder(src, null, girder_material)
if(!devastated)
- material.place_dismantled_product(src)
- if (!reinf_material)
+ if (reinf_material)
material.place_dismantled_product(src)
+ else
+ material.place_dismantled_product(src, 2)
for(var/obj/O in src.contents) //Eject contents!
if(istype(O,/obj/structure/sign/poster))
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
index 289a3b75ce9..60634ecc5dd 100644
--- a/code/game/turfs/space/space.dm
+++ b/code/game/turfs/space/space.dm
@@ -84,6 +84,7 @@
if(istype(C, /obj/item/stack/rods))
var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
if(L)
+ L.upgrade(C, user)
return
var/obj/item/stack/rods/R = C
if (R.use(1))
diff --git a/code/game/world.dm b/code/game/world.dm
index 23a636120f3..3c40edb35e9 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -1,5 +1,19 @@
+/proc/prof_init()
+ var/lib
+
+ switch(world.system_type)
+ if(MS_WINDOWS) lib = "prof.dll"
+ if(UNIX) lib = "libprof.so"
+ else CRASH("unsupported platform")
+
+ var/init = LIBCALL(lib, "init")()
+ if("0" != init) CRASH("[lib] init error: [init]")
+
#define RECOMMENDED_VERSION 513
/world/New()
+ #ifdef TRACY
+ prof_init()
+ #endif
world_startup_time = world.timeofday
rollover_safety_date = world.realtime - world.timeofday // 00:00 today (ish, since floating point error with world.realtime) of today
to_world_log("Map Loading Complete")
diff --git a/code/modules/admin/verbs/modify_robot.dm b/code/modules/admin/verbs/modify_robot.dm
index 68a7263fa28..f381c069681 100644
--- a/code/modules/admin/verbs/modify_robot.dm
+++ b/code/modules/admin/verbs/modify_robot.dm
@@ -10,11 +10,42 @@
return
if(!target.module)
- if(tgui_alert(usr, "This robot has not yet selected a module. Would you like to toggle combat module override?","Confirm",list("Yes","No"))!="Yes")
- return
- target.crisis_override = !target.crisis_override
- to_chat(usr, "You [target.crisis_override? "enabled":"disabled"] [target]'s combat module overwrite.")
- return
+ var/list/pre_modification_options = list(MODIFIY_ROBOT_TOGGLE_ERT, MODIFIY_ROBOT_LIMIT_MODULES_ADD, MODIFIY_ROBOT_LIMIT_MODULES_REMOVE)
+ while(TRUE)
+ var/pre_modification_choice = tgui_input_list(usr, "Which pre module selection edits would you like to perform form [target]","Choice", pre_modification_options)
+ if(!pre_modification_choice || pre_modification_choice == "Cancel")
+ return
+ switch(pre_modification_choice)
+ if(MODIFIY_ROBOT_TOGGLE_ERT)
+ if(tgui_alert(usr, "This robot has not yet selected a module. Would you like to toggle combat module override?","Confirm",list("Yes","No"))!="Yes")
+ continue
+ target.crisis_override = !target.crisis_override
+ to_chat(usr, "You [target.crisis_override? "enabled":"disabled"] [target]'s combat module overwrite.")
+ continue
+ if(MODIFIY_ROBOT_LIMIT_MODULES_ADD)
+ if(target.restrict_modules_to.len)
+ to_chat(usr, "[target]'s modules are already restricted. For details you can use the remove verb to show all active restrictions.")
+ var/list/possible_options = list()
+ for(var/entry in robot_modules)
+ if(!target.restrict_modules_to.Find(entry))
+ possible_options += entry
+ while(TRUE)
+ var/selected_type = tgui_input_list(usr, "Please select the module type to add to the robot's restrictions. This will limit the module choices to the added types only.", "Module types", possible_options)
+ if(!selected_type || selected_type == "Cancel")
+ break
+ possible_options -= selected_type
+ target.restrict_modules_to += selected_type
+ to_chat(usr, "You added [selected_type] to [target]'s possible modules list.")
+ continue
+ if(MODIFIY_ROBOT_LIMIT_MODULES_REMOVE)
+ while(TRUE)
+ var/selected_type = tgui_input_list(usr, "Please select the module type to remove. Removing all module types here will allow default station module selection.", "Module types", target.restrict_modules_to)
+ if(!selected_type || selected_type == "Cancel")
+ to_chat(usr, "[target] uses the default module list without special restrictions.")
+ break
+ target.restrict_modules_to -= selected_type
+ to_chat(usr, "You removed [selected_type] from [target]'s possible modules list.")
+ continue
if(!target.module.modules)
return
@@ -59,6 +90,8 @@
robot.module.contents.Remove(add_item)
target.module.modules.Add(add_item)
target.module.contents.Add(add_item)
+ spawn(0) //ChompEDIT Must be after to allow the movement to finish
+ SEND_SIGNAL(add_item, COMSIG_OBSERVER_MOVED)//ChompEDIT - report the movement since setting loc doesn't call Move or Moved
target.hud_used.update_robot_modules_display()
to_chat(usr, "You added \"[add_item]\" to [target].")
if(istype(add_item, /obj/item/stack/))
diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm
index 7725ad92728..d4a405e35c0 100644
--- a/code/modules/admin/verbs/pray.dm
+++ b/code/modules/admin/verbs/pray.dm
@@ -17,7 +17,7 @@
return
var/icon/cross = icon('icons/obj/storage.dmi',"bible")
- var/msg = "" + span_blue("\icon[cross][bicon(cross)] " + span_purple("PRAY: ") + "[key_name(src, 1)] [ADMIN_QUE(src)] [ADMIN_PP(src)] [ADMIN_VV(src)] [ADMIN_SM(src)] ([admin_jump_link(src, src)]) [ADMIN_CA(src)] [ADMIN_SC(src)] [ADMIN_SMITE(src)]: [raw_msg]") + ""
+ var/msg = "" + span_blue("[icon2html(cross, GLOB.admins)] " + span_purple("PRAY: ") + "[key_name(src, 1)] [ADMIN_QUE(src)] [ADMIN_PP(src)] [ADMIN_VV(src)] [ADMIN_SM(src)] ([admin_jump_link(src, src)]) [ADMIN_CA(src)] [ADMIN_SC(src)] [ADMIN_SMITE(src)]: [raw_msg]") + ""
for(var/client/C in GLOB.admins)
if(R_ADMIN|R_EVENT & C.holder.rights)
diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm
index 78e85bfa3e0..f8bf5cd9dcc 100644
--- a/code/modules/ai/ai_holder.dm
+++ b/code/modules/ai/ai_holder.dm
@@ -20,7 +20,13 @@
return ..()
/mob/living/Destroy()
- QDEL_NULL(ai_holder)
+ if(ai_holder)
+ ai_holder.holder = null
+ ai_holder.UnregisterSignal(src,COMSIG_MOB_STATCHANGE)
+ if(ai_holder.faction_friends && ai_holder.faction_friends.len) //This list is shared amongst the faction
+ ai_holder.faction_friends -= src
+ ai_holder.faction_friends = null
+ QDEL_NULL(ai_holder)
return ..()
/mob/living/Login()
@@ -222,7 +228,7 @@
holder = new_holder
home_turf = get_turf(holder)
manage_processing(AI_PROCESSING)
- GLOB.stat_set_event.register(holder, src, PROC_REF(holder_stat_change))
+ RegisterSignal(holder, COMSIG_MOB_STATCHANGE, PROC_REF(holder_stat_change))
..()
/datum/ai_holder/Destroy()
diff --git a/code/modules/ai/ai_holder_combat_unseen.dm b/code/modules/ai/ai_holder_combat_unseen.dm
index 868191a43f5..efbfc1b5f5e 100644
--- a/code/modules/ai/ai_holder_combat_unseen.dm
+++ b/code/modules/ai/ai_holder_combat_unseen.dm
@@ -3,11 +3,11 @@
// Used when a target is out of sight or invisible.
/datum/ai_holder/proc/engage_unseen_enemy()
ai_log("engage_unseen_enemy() : Entering.", AI_LOG_TRACE)
-
+
// Also handled in strategic updates but handling it here allows for more fine resolution timeouts
if((lose_target_time+lose_target_timeout) <= world.time)
return find_target()
-
+
// Lets do some last things before giving up.
if(conserve_ammo || !holder.ICheckRangedAttack(target_last_seen_turf))
// We conserve ammo (or can't shoot) so walk closer
@@ -29,7 +29,7 @@
on_engagement(T)
if(firing_lanes && !test_projectile_safety(T))
step_rand(holder)
- holder.face_atom(T)
+ holder?.face_atom(T)
return ATTACK_FAILED
return ranged_attack(T)
@@ -60,7 +60,7 @@
/obj/structure/stairs/top,
/obj/structure/stairs/bottom
)
-
+
if(intelligence_level >= AI_SMART)
possible_escape_types += /obj/structure/ladder
@@ -69,18 +69,18 @@
continue // Not something they could have escaped through
if(turn(holder.dir, 180) & get_dir(get_turf(holder), get_turf(A)))
continue // Surely, they couldn't have escaped *behind* us!
-
+
if(istype(A, /obj/machinery/door))
var/obj/machinery/door/D = A
if(D.glass) // Surely, they couldn't hide behind a transparent door!
continue
if(D.density && intelligence_level < AI_SMART) // Surely, they couldn't have escaped through a *closed* door
continue
-
+
var/dist = get_dist(holder, A)
if(dist == closest_dist)
closest_escape += A
-
+
else if(dist < closest_dist)
closest_escape.Cut()
closest_escape += A
@@ -89,5 +89,3 @@
if(closest_escape.len)
return pick(closest_escape)
return null
-
-
diff --git a/code/modules/ai/ai_holder_cooperation.dm b/code/modules/ai/ai_holder_cooperation.dm
index dd6228f4600..97f3781ad5c 100644
--- a/code/modules/ai/ai_holder_cooperation.dm
+++ b/code/modules/ai/ai_holder_cooperation.dm
@@ -18,8 +18,9 @@
build_faction_friends()
/datum/ai_holder/Destroy()
- if(faction_friends.len) //This list is shared amongst the faction
- faction_friends -= src
+ if(faction_friends)
+ if(faction_friends.len) //This list is shared amongst the faction
+ faction_friends -= src
return ..()
// Handles everything about that list.
@@ -114,4 +115,3 @@
add_attacker(their_target) // We won't wait and 'warn' them while they're stabbing our ally
set_follow(friend, 10 SECONDS)
ai_log("help_requested() : Exiting.", AI_LOG_DEBUG)
-
diff --git a/code/modules/ai/ai_holder_subtypes/simple_mob_ai.dm b/code/modules/ai/ai_holder_subtypes/simple_mob_ai.dm
index c23a4d92e6e..4455b47072b 100644
--- a/code/modules/ai/ai_holder_subtypes/simple_mob_ai.dm
+++ b/code/modules/ai/ai_holder_subtypes/simple_mob_ai.dm
@@ -187,7 +187,7 @@
/datum/ai_holder/simple_mob/humanoid/hostile/post_ranged_attack(atom/A)
//Pick a random turf to step into
var/turf/T = get_step(holder, pick(alldirs))
- if(check_trajectory(A, T)) // Can we even hit them from there?
+ if((A in check_trajectory(A, T))) // Can we even hit them from there?
holder.IMove(T)
holder.face_atom(A)
@@ -197,4 +197,3 @@
/datum/ai_holder/simple_mob/passive/speedy
base_wander_delay = 1
-
diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm
index 8c0a38b4ff0..b918321f8b3 100644
--- a/code/modules/ai/ai_holder_targeting.dm
+++ b/code/modules/ai/ai_holder_targeting.dm
@@ -82,6 +82,8 @@
target = new_target
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(remove_target))
+
if(target != null)
lose_target_time = 0
track_target_position()
@@ -189,6 +191,7 @@
ai_log("lose_target() : Entering.", AI_LOG_TRACE)
if(target)
ai_log("lose_target() : Had a target, setting to null and LTT.", AI_LOG_DEBUG)
+ UnregisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(remove_target))
target = null
lose_target_time = world.time
@@ -204,6 +207,7 @@
/datum/ai_holder/proc/remove_target()
ai_log("remove_target() : Entering.", AI_LOG_TRACE)
if(target)
+ UnregisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(remove_target))
target = null
lose_target_time = 0
diff --git a/code/modules/ai/say_list.dm b/code/modules/ai/say_list.dm
index f2bd5c028ed..fb4e66651ef 100644
--- a/code/modules/ai/say_list.dm
+++ b/code/modules/ai/say_list.dm
@@ -134,4 +134,4 @@
emote_hear = list("hisses")
/datum/say_list/crab
- emote_hear = list("hisses")
\ No newline at end of file
+ emote_hear = list("hisses")
diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm
index 110a9cee35f..32ee55d553c 100644
--- a/code/modules/assembly/holder.dm
+++ b/code/modules/assembly/holder.dm
@@ -151,7 +151,7 @@
if(!D)
return 0
if(!secured)
- visible_message("\icon[src][bicon(src)] *beep* *beep*", "*beep* *beep*")
+ visible_message("[icon2html(src,viewers(src))] *beep* *beep*", "*beep* *beep*")
if((normal) && (a_right) && (a_left))
if(a_right != D)
a_right.pulsed(0)
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index fb5566504ef..7c6ce614589 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -98,7 +98,7 @@
pulse(0)
QDEL_LIST_NULL(i_beams) //They will get recreated next process() if the situation is still appropriate
if(!holder)
- visible_message("\icon[src][bicon(src)] *beep* *beep*")
+ visible_message("[icon2html(src,viewers(src))] *beep* *beep*")
/obj/item/device/assembly/infra/tgui_interact(mob/user, datum/tgui/ui)
if(!secured)
@@ -178,4 +178,4 @@
return
if(istype(AM, /obj/effect/beam))
return
- hit()
\ No newline at end of file
+ hit()
diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm
index 7c12d5fb039..959d4566d24 100644
--- a/code/modules/assembly/proximity.dm
+++ b/code/modules/assembly/proximity.dm
@@ -47,7 +47,7 @@
var/turf/mainloc = get_turf(src)
pulse(0)
if(!holder)
- mainloc.visible_message("\icon[src][bicon(src)] *beep* *beep*", "*beep* *beep*")
+ mainloc.visible_message("[icon2html(src,viewers(src))] *beep* *beep*", "*beep* *beep*")
/obj/item/device/assembly/prox_sensor/process()
if(scanning)
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index a87a13a5765..3232e602d06 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -116,7 +116,7 @@
if(!holder)
for(var/mob/O in hearers(1, src.loc))
- O.show_message("\icon[src][bicon(src)] *beep* *beep*", 3, "*beep* *beep*", 2)
+ O.show_message("[icon2html(src, O.client)] *beep* *beep*", 3, "*beep* *beep*", 2)
/obj/item/device/assembly/signaler/proc/set_frequency(new_frequency)
if(!frequency)
diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm
index dd13a1e4a21..fb47a020c32 100644
--- a/code/modules/assembly/timer.dm
+++ b/code/modules/assembly/timer.dm
@@ -44,7 +44,7 @@
return 0
pulse(0)
if(!holder)
- visible_message("\icon[src][bicon(src)] *beep* *beep*", "*beep* *beep*")
+ visible_message("[icon2html(src,viewers(src))] *beep* *beep*", "*beep* *beep*")
/obj/item/device/assembly/timer/process()
if(timing && time-- <= 0)
diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm
index 7c8eee7fdfb..2783f92b767 100644
--- a/code/modules/assembly/voice.dm
+++ b/code/modules/assembly/voice.dm
@@ -13,7 +13,7 @@
recorded = msg
listening = 0
var/turf/T = get_turf(src) //otherwise it won't work in hand
- T.visible_message("\icon[src][bicon(src)] beeps, \"Activation message is '[recorded]'.\"")
+ T.visible_message("[icon2html(src,viewers(src))] beeps, \"Activation message is '[recorded]'.\"")
else
if(findtext(msg, recorded))
pulse(0)
@@ -23,7 +23,7 @@
if(!holder)
listening = !listening
var/turf/T = get_turf(src)
- T.visible_message("\icon[src][bicon(src)] beeps, \"[listening ? "Now" : "No longer"] recording input.\"")
+ T.visible_message("[icon2html(src,viewers(src))] beeps, \"[listening ? "Now" : "No longer"] recording input.\"")
/obj/item/device/assembly/voice/attack_self(mob/user)
@@ -34,4 +34,4 @@
/obj/item/device/assembly/voice/toggle_secure()
. = ..()
- listening = 0
\ No newline at end of file
+ listening = 0
diff --git a/code/modules/asset_cache/asset_cache.dm b/code/modules/asset_cache/asset_cache.dm
deleted file mode 100644
index 53a30d4299a..00000000000
--- a/code/modules/asset_cache/asset_cache.dm
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
-Asset cache quick users guide:
-
-Make a datum in asset_list_items.dm with your assets for your thing.
-Checkout asset_list.dm for the helper subclasses
-The simple subclass will most like be of use for most cases.
-Then call get_asset_datum() with the type of the datum you created and store the return
-Then call .send(client) on that stored return value.
-
-Note: If your code uses output() with assets you will need to call asset_flush on the client and wait for it to return before calling output(). You only need do this if .send(client) returned TRUE
-*/
-
-//When sending mutiple assets, how many before we give the client a quaint little sending resources message
-#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
-
-//This proc sends the asset to the client, but only if it needs it.
-//This proc blocks(sleeps) unless verify is set to false
-/proc/send_asset(client/client, asset_name)
- return send_asset_list(client, list(asset_name))
-
-/// Sends a list of assets to a client
-/// This proc will no longer block, use client.asset_flush() if you to need know when the client has all assets (such as for output()). (This is not required for browse() calls as they use the same message queue as asset sends)
-/// client - a client or mob
-/// asset_list - A list of asset filenames to be sent to the client.
-/// Returns TRUE if any assets were sent.
-/proc/send_asset_list(client/client, list/asset_list)
- if(!istype(client))
- if(ismob(client))
- var/mob/M = client
- if(M.client)
- client = M.client
- else
- return
- else
- return
-
- var/list/unreceived = list()
-
- for (var/asset_name in asset_list)
- var/datum/asset_cache_item/asset = SSassets.cache[asset_name]
- if (!asset)
- continue
- var/asset_file = asset.resource
- if (!asset_file)
- continue
-
- var/asset_md5 = asset.md5
- if (client.sent_assets[asset_name] == asset_md5)
- continue
- unreceived[asset_name] = asset_md5
-
- if (unreceived.len)
- if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
- to_chat(client, "Sending Resources...")
-
- for(var/asset in unreceived)
- var/datum/asset_cache_item/ACI
- if ((ACI = SSassets.cache[asset]))
- log_asset("Sending asset [asset] to client [client]")
- client << browse_rsc(ACI.resource, asset)
-
- client.sent_assets |= unreceived
- addtimer(CALLBACK(client, /client/proc/asset_cache_update_json), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
- return TRUE
- return FALSE
-
-//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start.
-//The proc calls procs that sleep for long times.
-/proc/getFilesSlow(client/client, list/files, register_asset = TRUE, filerate = 3)
- var/startingfilerate = filerate
- for(var/file in files)
- if (!client)
- break
- if (register_asset)
- register_asset(file, files[file])
-
- if (send_asset(client, file))
- if (!(--filerate))
- filerate = startingfilerate
- client.asset_flush()
- stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
-
-//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up.
-//icons and virtual assets get copied to the dyn rsc before use
-/proc/register_asset(asset_name, asset)
- var/datum/asset_cache_item/ACI = new(asset_name, asset)
-
- //this is technically never something that was supported and i want metrics on how often it happens if at all.
- if (SSassets.cache[asset_name])
- var/datum/asset_cache_item/OACI = SSassets.cache[asset_name]
- if (OACI.md5 != ACI.md5)
- stack_trace("ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]")
- else
- var/list/stacktrace = gib_stack_trace()
- log_asset("WARNING: dupe asset added to the asset cache: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]\n[stacktrace.Join("\n")]")
- SSassets.cache[asset_name] = ACI
- return ACI
-
-/// Returns the url of the asset, currently this is just its name, here to allow further work cdn'ing assets.
-/// Can be given an asset as well, this is just a work around for buggy edge cases where two assets may have the same name, doesn't matter now, but it will when the cdn comes.
-/proc/get_asset_url(asset_name, asset = null)
- var/datum/asset_cache_item/ACI = SSassets.cache[asset_name]
- return ACI?.url
-
-//Generated names do not include file extention.
-//Used mainly for code that deals with assets in a generic way
-//The same asset will always lead to the same asset name
-/proc/generate_asset_name(file)
- return "asset.[md5(fcopy_rsc(file))]"
-
diff --git a/code/modules/asset_cache/asset_cache_client.dm b/code/modules/asset_cache/asset_cache_client.dm
index e25b9969863..3cff8cb41f3 100644
--- a/code/modules/asset_cache/asset_cache_client.dm
+++ b/code/modules/asset_cache/asset_cache_client.dm
@@ -1,5 +1,5 @@
-/// Process asset cache client topic calls for "asset_cache_confirm_arrival=[INT]"
+/// Process asset cache client topic calls for `"asset_cache_confirm_arrival=[INT]"`
/client/proc/asset_cache_confirm_arrival(job_id)
var/asset_cache_job = round(text2num(job_id))
//because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit.
@@ -10,17 +10,9 @@
return asset_cache_job || TRUE
-/// Process asset cache client topic calls for "asset_cache_preload_data=[HTML+JSON_STRING]
+/// Process asset cache client topic calls for `"asset_cache_preload_data=[HTML+JSON_STRING]"`
/client/proc/asset_cache_preload_data(data)
- /*var/jsonend = findtextEx(data, "{{{ENDJSONDATA}}}")
- if (!jsonend)
- CRASH("invalid asset_cache_preload_data, no jsonendmarker")*/
- //var/json = html_decode(copytext(data, 1, jsonend))
var/json = data
-
- // This is a stupid workaround to BYOND injecting this pngfix mess into IE7 clients browse()
- json = replacetext(json, "", "")
-
var/list/preloaded_assets = json_decode(json)
for (var/preloaded_asset in preloaded_assets)
@@ -30,19 +22,17 @@
sent_assets |= preloaded_assets
-/// Updates the client side stored html/json combo file used to keep track of what assets the client has between restarts/reconnects.
-/client/proc/asset_cache_update_json(verify = FALSE, list/new_assets = list())
+/// Updates the client side stored json file used to keep track of what assets the client has between restarts/reconnects.
+/client/proc/asset_cache_update_json()
if (world.time - connection_time < 10 SECONDS) //don't override the existing data file on a new connection
return
- if (!islist(new_assets))
- new_assets = list("[new_assets]" = md5(SSassets.cache[new_assets]))
- src << browse(json_encode(new_assets|sent_assets), "file=asset_data.json&display=0")
+ src << browse(json_encode(sent_assets), "file=asset_data.json&display=0")
-/// Blocks until all currently sending browser assets have been sent.
+/// Blocks until all currently sending browse and browse_rsc assets have been sent.
/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends.
/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away.
-/client/proc/asset_flush(timeout = 50)
+/client/proc/browse_queue_flush(timeout = 50)
var/job = ++last_asset_job
var/t = 0
var/timeout_time = timeout
diff --git a/code/modules/asset_cache/asset_cache_item.dm b/code/modules/asset_cache/asset_cache_item.dm
index 5f02e561c6f..ad4596bdedc 100644
--- a/code/modules/asset_cache/asset_cache_item.dm
+++ b/code/modules/asset_cache/asset_cache_item.dm
@@ -1,23 +1,54 @@
/**
- * # asset_cache_item
- *
- * An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed.
-**/
+ * # asset_cache_item
+ *
+ * An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed.
+ */
/datum/asset_cache_item
+ /// the name of this asset item, becomes the key in SSassets.cache list
var/name
- var/url
- var/md5
+ /// md5() of the file this asset item represents.
+ var/hash
+ /// the file this asset represents
var/resource
+ /// our file extension e.g. .png, .gif, etc
+ var/ext = ""
+ /// Should this file also be sent via the legacy browse_rsc system
+ /// when cdn transports are enabled?
+ var/legacy = FALSE
+ /// Used by the cdn system to keep legacy css assets with their parent
+ /// css file. (css files resolve urls relative to the css file, so the
+ /// legacy system can't be used if the css file itself could go out over
+ /// the cdn)
+ var/namespace = null
+ /// True if this is the parent css or html file for an asset's namespace
+ var/namespace_parent = FALSE
+ /// TRUE for keeping local asset names when browse_rsc backend is used
+ var/keep_local_name = FALSE
-/datum/asset_cache_item/New(name, file)
+///pass in a valid file_hash if you have one to save it from needing to do it again.
+///pass in a valid dmi file path string e.g. "icons/path/to/dmi_file.dmi" to make generating the hash less expensive
+/datum/asset_cache_item/New(name, file, file_hash, dmi_file_path)
if (!isfile(file))
file = fcopy_rsc(file)
- md5 = md5(file)
- if (!md5)
- md5 = md5(fcopy_rsc(file))
- if (!md5)
- CRASH("invalid asset sent to asset cache")
- log_world("asset cache unexpected success of second fcopy_rsc")
+
+ hash = file_hash
+
+ //the given file is directly from a dmi file and is thus in the rsc already, we know that its file_hash will be correct
+ if(!hash)
+ if(dmi_file_path)
+ hash = md5(file)
+ else
+ hash = md5asfile(file) //icons sent to the rsc md5 incorrectly when theyre given incorrect data
+ if (!hash)
+ CRASH("invalid asset sent to asset cache")
src.name = name
- url = name
+ var/extstart = findlasttext(name, ".")
+ if (extstart)
+ ext = ".[copytext(name, extstart+1)]"
resource = file
+
+/datum/asset_cache_item/vv_edit_var(var_name, var_value)
+ return FALSE
+
+/datum/asset_cache_item/CanProcCall(procname)
+ return FALSE
diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm
index 4ce9dcf6fc0..c9e02cb9276 100644
--- a/code/modules/asset_cache/asset_list.dm
+++ b/code/modules/asset_cache/asset_list.dm
@@ -1,3 +1,4 @@
+#define ASSET_CROSS_ROUND_CACHE_DIRECTORY "tmp/assets"
//These datums are used to populate the asset cache, the proc "register()" does this.
//Place any asset datums you create in asset_list_items.dm
@@ -6,46 +7,101 @@
GLOBAL_LIST_EMPTY(asset_datums)
//get an assetdatum or make a new one
-/proc/get_asset_datum(type)
+//does NOT ensure it's filled, if you want that use get_asset_datum()
+/proc/load_asset_datum(type)
return GLOB.asset_datums[type] || new type()
+/proc/get_asset_datum(type)
+ var/datum/asset/loaded_asset = GLOB.asset_datums[type] || new type()
+ return loaded_asset.ensure_ready()
+
/datum/asset
var/_abstract = /datum/asset
+ var/cached_serialized_url_mappings
+ var/cached_serialized_url_mappings_transport_type
+
+ /// Whether or not this asset should be loaded in the "early assets" SS
+ var/early = FALSE
+
+ /// Whether or not this asset can be cached across rounds of the same commit under the `CACHE_ASSETS` config.
+ /// This is not a *guarantee* the asset will be cached. Not all asset subtypes respect this field, and the
+ /// config can, of course, be disabled.
+ /// Disable this if your asset can change between rounds on the same exact version of the code.
+ var/cross_round_cachable = FALSE
/datum/asset/New()
GLOB.asset_datums[type] = src
register()
+/// Stub that allows us to react to something trying to get us
+/// Not useful here, more handy for sprite sheets
+/datum/asset/proc/ensure_ready()
+ return src
+
+/// Stub to hook into if your asset is having its generation queued by SSasset_loading
+/datum/asset/proc/queued_generation()
+ CRASH("[type] inserted into SSasset_loading despite not implementing /proc/queued_generation")
+
/datum/asset/proc/get_url_mappings()
return list()
+/// Returns a cached tgui message of URL mappings
+/datum/asset/proc/get_serialized_url_mappings()
+ if (isnull(cached_serialized_url_mappings) || cached_serialized_url_mappings_transport_type != SSassets.transport.type)
+ cached_serialized_url_mappings = TGUI_CREATE_MESSAGE("asset/mappings", get_url_mappings())
+ cached_serialized_url_mappings_transport_type = SSassets.transport.type
+
+ return cached_serialized_url_mappings
+
/datum/asset/proc/register()
return
/datum/asset/proc/send(client)
return
+/// Returns whether or not the asset should attempt to read from cache
+/datum/asset/proc/should_refresh()
+ return !cross_round_cachable || !config.cache_assets
-//If you don't need anything complicated.
+/// Simply takes any generated file and saves it to the round-specific /logs folder. Useful for debugging potential issues with spritesheet generation/display.
+/// Only called when the SAVE_SPRITESHEETS config option is uncommented.
+/datum/asset/proc/save_to_logs(file_name, file_location)
+ var/asset_path = "[log_path]/generated_assets/[file_name]"
+ fdel(asset_path) // just in case, sadly we can't use rust_g stuff here.
+ fcopy(file_location, asset_path)
+
+/// If you don't need anything complicated.
/datum/asset/simple
_abstract = /datum/asset/simple
+ /// list of assets for this datum in the form of:
+ /// asset_filename = asset_file. At runtime the asset_file will be
+ /// converted into a asset_cache datum.
var/assets = list()
+ /// Set to true to have this asset also be sent via the legacy browse_rsc
+ /// system when cdn transports are enabled?
+ var/legacy = FALSE
+ /// TRUE for keeping local asset names when browse_rsc backend is used
+ var/keep_local_name = FALSE
/datum/asset/simple/register()
for(var/asset_name in assets)
- assets[asset_name] = register_asset(asset_name, assets[asset_name])
+ var/datum/asset_cache_item/ACI = SSassets.transport.register_asset(asset_name, assets[asset_name])
+ if (!ACI)
+ log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
+ continue
+ if (legacy)
+ ACI.legacy = legacy
+ if (keep_local_name)
+ ACI.keep_local_name = keep_local_name
+ assets[asset_name] = ACI
/datum/asset/simple/send(client)
- . = send_asset_list(client, assets)
+ . = SSassets.transport.send_assets(client, assets)
/datum/asset/simple/get_url_mappings()
. = list()
for (var/asset_name in assets)
- var/datum/asset_cache_item/ACI = assets[asset_name]
- if (!ACI)
- continue
- .[asset_name] = ACI.url
-
+ .[asset_name] = SSassets.transport.get_asset_url(asset_name, assets[asset_name])
// For registering or sending multiple others at once
/datum/asset/group
@@ -54,7 +110,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/group/register()
for(var/type in children)
- get_asset_datum(type)
+ load_asset_datum(type)
/datum/asset/group/send(client/C)
for(var/type in children)
@@ -78,40 +134,122 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/spritesheet
_abstract = /datum/asset/spritesheet
+ cross_round_cachable = TRUE
var/name
+ /// List of arguments to pass into queuedInsert
+ /// Exists so we can queue icon insertion, mostly for stuff like preferences
+ var/list/to_generate = list()
var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped)
var/list/sprites = list() // "foo_bar" -> list("32x32", 5)
+ var/list/cached_spritesheets_needed
+ var/generating_cache = FALSE
+ var/fully_generated = FALSE
+ /// If this asset should be fully loaded on new
+ /// Defaults to false so we can process this stuff nicely
+ var/load_immediately = FALSE
+ VAR_PRIVATE
+ // Kept in state so that the result is the same, even when the files are created, for this run
+ should_refresh = null
+
+/datum/asset/spritesheet/proc/should_load_immediately()
+#ifdef DO_NOT_DEFER_ASSETS
+ return TRUE
+#else
+ return load_immediately
+#endif
+
+
+/datum/asset/spritesheet/should_refresh()
+ if (..())
+ return TRUE
+
+ if (isnull(should_refresh))
+ // `fexists` seems to always fail on static-time
+ should_refresh = !fexists(css_cache_filename()) || !fexists(data_cache_filename())
+
+ return should_refresh
/datum/asset/spritesheet/register()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
if (!name)
CRASH("spritesheet [type] cannot register without a name")
+
+ if (!should_refresh() && read_from_cache())
+ fully_generated = TRUE
+ return
+
+ // If it's cached, may as well load it now, while the loading is cheap
+ if(config.cache_assets && cross_round_cachable)
+ load_immediately = TRUE
+
+ create_spritesheets()
+ if(should_load_immediately())
+ realize_spritesheets(yield = FALSE)
+ else
+ SSasset_loading.queue_asset(src)
+
+/datum/asset/spritesheet/proc/realize_spritesheets(yield)
+ if(fully_generated)
+ return
+ while(length(to_generate))
+ var/list/stored_args = to_generate[to_generate.len]
+ to_generate.len--
+ queuedInsert(arglist(stored_args))
+ if(yield && TICK_CHECK)
+ return
+
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)
- text2file(generate_css(), fname)
- register_asset(res_name, fcopy_rsc(fname))
- fdel(fname)
-
-/datum/asset/spritesheet/send(client/C)
+ SSassets.transport.register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
+ var/css_name = "spritesheet_[name].css"
+ var/file_directory = "data/spritesheets/[css_name]"
+ fdel(file_directory)
+ text2file(generate_css(), file_directory)
+ SSassets.transport.register_asset(css_name, fcopy_rsc(file_directory))
+
+ if(config.save_spritesheets)
+ save_to_logs(file_name = css_name, file_location = file_directory)
+
+ fdel(file_directory)
+
+ if (config.cache_assets && cross_round_cachable)
+ write_to_cache()
+ fully_generated = TRUE
+ // If we were ever in there, remove ourselves
+ SSasset_loading.dequeue_asset(src)
+
+/datum/asset/spritesheet/queued_generation()
+ realize_spritesheets(yield = TRUE)
+
+/datum/asset/spritesheet/ensure_ready()
+ if(!fully_generated)
+ realize_spritesheets(yield = FALSE)
+ return ..()
+
+/datum/asset/spritesheet/send(client/client)
if (!name)
return
+
+ if (!should_refresh())
+ return send_from_cache(client)
+
var/all = list("spritesheet_[name].css")
for(var/size_id in sizes)
all += "[name]_[size_id].png"
- . = send_asset_list(C, all)
+ . = SSassets.transport.send_assets(client, all)
/datum/asset/spritesheet/get_url_mappings()
if (!name)
return
- . = list("spritesheet_[name].css" = get_asset_url("spritesheet_[name].css"))
- for(var/size_id in sizes)
- .["[name]_[size_id].png"] = get_asset_url("[name]_[size_id].png")
+ if (!should_refresh())
+ return get_cached_url_mappings()
+ . = list("spritesheet_[name].css" = SSassets.transport.get_asset_url("spritesheet_[name].css"))
+ for(var/size_id in sizes)
+ .["[name]_[size_id].png"] = SSassets.transport.get_asset_url("[name]_[size_id].png")
/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes)
for(var/size_id in sizes_to_strip)
@@ -120,13 +258,19 @@ GLOBAL_LIST_EMPTY(asset_datums)
continue
// save flattened version
- var/fname = "data/spritesheets/[name]_[size_id].png"
- fcopy(size[SPRSZ_ICON], fname)
- var/error = rustg_dmi_strip_metadata(fname)
+ var/png_name = "[name]_[size_id].png"
+ var/file_directory = "data/spritesheets/[png_name]"
+ fcopy(size[SPRSZ_ICON], file_directory)
+ var/error = rustg_dmi_strip_metadata(file_directory)
if(length(error))
- stack_trace("Failed to strip [name]_[size_id].png: [error]")
- size[SPRSZ_STRIPPED] = icon(fname)
- fdel(fname)
+ stack_trace("Failed to strip [png_name]: [error]")
+ size[SPRSZ_STRIPPED] = icon(file_directory)
+
+ // this is useful here for determining if weird sprite issues (like having a white background) are a cause of what we're doing DM-side or not since we can see the full flattened thing at-a-glance.
+ if(config.save_spritesheets)
+ save_to_logs(file_name = png_name, file_location = file_directory)
+
+ fdel(file_directory)
/datum/asset/spritesheet/proc/generate_css()
var/list/out = list()
@@ -134,7 +278,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
for (var/size_id in sizes)
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
- out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[get_asset_url("[name]_[size_id].png")]') no-repeat;}"
+ out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[get_background_url("[name]_[size_id].png")]') no-repeat;}"
for (var/sprite_id in sprites)
var/sprite = sprites[sprite_id]
@@ -152,10 +296,109 @@ GLOBAL_LIST_EMPTY(asset_datums)
return out.Join("\n")
+/datum/asset/spritesheet/proc/css_cache_filename()
+ return "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css"
+
+/datum/asset/spritesheet/proc/data_cache_filename()
+ return "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].json"
+
+/datum/asset/spritesheet/proc/read_from_cache()
+ return read_css_from_cache() && read_data_from_cache()
+
+/datum/asset/spritesheet/proc/read_css_from_cache()
+ var/replaced_css = file2text(css_cache_filename())
+
+ var/regex/find_background_urls = regex(@"background:url\('%(.+?)%'\)", "g")
+ while (find_background_urls.Find(replaced_css))
+ var/asset_id = find_background_urls.group[1]
+ var/asset_cache_item = SSassets.transport.register_asset(asset_id, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[asset_id]")
+ var/asset_url = SSassets.transport.get_asset_url(asset_cache_item = asset_cache_item)
+ replaced_css = replacetext(replaced_css, find_background_urls.match, "background:url('[asset_url]')")
+ LAZYADD(cached_spritesheets_needed, asset_id)
+
+ var/finalized_name = "spritesheet_[name].css"
+ var/replaced_css_filename = "data/spritesheets/[finalized_name]"
+ rustg_file_write(replaced_css, replaced_css_filename)
+ SSassets.transport.register_asset(finalized_name, replaced_css_filename)
+
+ if(config.save_spritesheets)
+ save_to_logs(file_name = finalized_name, file_location = replaced_css_filename)
+
+ fdel(replaced_css_filename)
+
+ return TRUE
+
+/datum/asset/spritesheet/proc/read_data_from_cache()
+ var/json = json_decode(file2text(data_cache_filename()))
+
+ if (islist(json["sprites"]))
+ sprites = json["sprites"]
+
+ return TRUE
+
+/datum/asset/spritesheet/proc/send_from_cache(client/client)
+ if (isnull(cached_spritesheets_needed))
+ stack_trace("cached_spritesheets_needed was null when sending assets from [type] from cache")
+ cached_spritesheets_needed = list()
+
+ return SSassets.transport.send_assets(client, cached_spritesheets_needed + "spritesheet_[name].css")
+
+/// Returns the URL to put in the background:url of the CSS asset
+/datum/asset/spritesheet/proc/get_background_url(asset)
+ if (generating_cache)
+ return "%[asset]%"
+ else
+ return SSassets.transport.get_asset_url(asset)
+
+/datum/asset/spritesheet/proc/write_to_cache()
+ write_css_to_cache()
+ write_data_to_cache()
+
+/datum/asset/spritesheet/proc/write_css_to_cache()
+ for (var/size_id in sizes)
+ fcopy(SSassets.cache["[name]_[size_id].png"].resource, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name]_[size_id].png")
+
+ generating_cache = TRUE
+ var/mock_css = generate_css()
+ generating_cache = FALSE
+
+ rustg_file_write(mock_css, css_cache_filename())
+
+/datum/asset/spritesheet/proc/write_data_to_cache()
+ rustg_file_write(json_encode(list(
+ "sprites" = sprites,
+ )), data_cache_filename())
+
+/datum/asset/spritesheet/proc/get_cached_url_mappings()
+ var/list/mappings = list()
+ mappings["spritesheet_[name].css"] = SSassets.transport.get_asset_url("spritesheet_[name].css")
+
+ for (var/asset_name in cached_spritesheets_needed)
+ mappings[asset_name] = SSassets.transport.get_asset_url(asset_name)
+
+ return mappings
+
+/// Override this in order to start the creation of the spritehseet.
+/// This is where all your Insert, InsertAll, etc calls should be inside.
+/datum/asset/spritesheet/proc/create_spritesheets()
+ SHOULD_CALL_PARENT(FALSE)
+ CRASH("create_spritesheets() not implemented for [type]!")
+
/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
+ if(should_load_immediately())
+ queuedInsert(sprite_name, I, icon_state, dir, frame, moving)
+ else
+ to_generate += list(args.Copy())
+
+/datum/asset/spritesheet/proc/queuedInsert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
if (!I || !length(icon_states(I))) // that direction or state doesn't exist
return
+
+ //var/start_usage = world.tick_usage
+
+ //any sprite modifications we want to do (aka, coloring a greyscaled asset)
+ I = ModifyInserted(I)
var/size_id = "[I.Width()]x[I.Height()]"
var/size = sizes[size_id]
@@ -164,14 +407,34 @@ GLOBAL_LIST_EMPTY(asset_datums)
if (size)
var/position = size[SPRSZ_COUNT]++
+ // Icons are essentially representations of files + modifications
+ // Because of this, byond keeps them in a cache. It does this in a really dumb way tho
+ // It's essentially a FIFO queue. So after we do icon() some amount of times, our old icons go out of cache
+ // When this happens it becomes impossible to modify them, trying to do so will instead throw a
+ // "bad icon" error.
+ // What we're doing here is ensuring our icon is in the cache by refreshing it, so we can modify it w/o runtimes.
var/icon/sheet = size[SPRSZ_ICON]
+ var/icon/sheet_copy = icon(sheet)
size[SPRSZ_STRIPPED] = null
- sheet.Insert(I, icon_state=sprite_name)
+ sheet_copy.Insert(I, icon_state=sprite_name)
+ size[SPRSZ_ICON] = sheet_copy
+
sprites[sprite_name] = list(size_id, position)
else
sizes[size_id] = size = list(1, I, null)
sprites[sprite_name] = list(size_id, 0)
+ //SSblackbox.record_feedback("tally", "spritesheet_queued_insert_time", TICK_USAGE_TO_MS(start_usage), name)
+
+/**
+ * A simple proc handing the Icon for you to modify before it gets turned into an asset.
+ *
+ * Arguments:
+ * * I: icon being turned into an asset
+ */
+/datum/asset/spritesheet/proc/ModifyInserted(icon/pre_asset)
+ return pre_asset
+
/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions)
if (length(prefix))
prefix = "[prefix]-"
@@ -188,14 +451,14 @@ GLOBAL_LIST_EMPTY(asset_datums)
return {""}
/datum/asset/spritesheet/proc/css_filename()
- return get_asset_url("spritesheet_[name].css")
+ return SSassets.transport.get_asset_url("spritesheet_[name].css")
/datum/asset/spritesheet/proc/icon_tag(sprite_name)
var/sprite = sprites[sprite_name]
if (!sprite)
return null
var/size_id = sprite[SPR_SIZE]
- return {""}
+ return {""}
/datum/asset/spritesheet/proc/icon_class_name(sprite_name)
var/sprite = sprites[sprite_name]
@@ -204,6 +467,19 @@ GLOBAL_LIST_EMPTY(asset_datums)
var/size_id = sprite[SPR_SIZE]
return {"[name][size_id] [sprite_name]"}
+/**
+ * Returns the size class (ex design32x32) for a given sprite's icon
+ *
+ * Arguments:
+ * * sprite_name - The sprite to get the size of
+ */
+/datum/asset/spritesheet/proc/icon_size_id(sprite_name)
+ var/sprite = sprites[sprite_name]
+ if (!sprite)
+ return null
+ var/size_id = sprite[SPR_SIZE]
+ return "[name][size_id]"
+
#undef SPR_SIZE
#undef SPR_IDX
#undef SPRSZ_COUNT
@@ -211,14 +487,31 @@ GLOBAL_LIST_EMPTY(asset_datums)
#undef SPRSZ_STRIPPED
+/datum/asset/changelog_item
+ _abstract = /datum/asset/changelog_item
+ var/item_filename
+
+/datum/asset/changelog_item/New(date)
+ item_filename = sanitize_filename("[date].yml")
+ SSassets.transport.register_asset(item_filename, file("html/changelogs_ch/archive/" + item_filename))
+
+/datum/asset/changelog_item/send(client)
+ if (!item_filename)
+ return
+ . = SSassets.transport.send_assets(client, item_filename)
+
+/datum/asset/changelog_item/get_url_mappings()
+ if (!item_filename)
+ return
+ . = list("[item_filename]" = SSassets.transport.get_asset_url(item_filename))
+
/datum/asset/spritesheet/simple
_abstract = /datum/asset/spritesheet/simple
var/list/assets
-/datum/asset/spritesheet/simple/register()
+/datum/asset/spritesheet/simple/create_spritesheets()
for (var/key in assets)
Insert(key, assets[key])
- ..()
//Generates assets based on iconstates of a single icon
/datum/asset/simple/icon_states
@@ -243,7 +536,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
if (generic_icon_names)
asset_name = "[generate_asset_name(asset)].png"
- register_asset(asset_name, asset)
+ SSassets.transport.register_asset(asset_name, asset)
/datum/asset/simple/icon_states/multiple_icons
_abstract = /datum/asset/simple/icon_states/multiple_icons
@@ -253,4 +546,79 @@ GLOBAL_LIST_EMPTY(asset_datums)
for(var/i in icons)
..(i)
+/// Namespace'ed assets (for static css and html files)
+/// When sent over a cdn transport, all assets in the same asset datum will exist in the same folder, as their plain names.
+/// Used to ensure css files can reference files by url() without having to generate the css at runtime, both the css file and the files it depends on must exist in the same namespace asset datum. (Also works for html)
+/// For example `blah.css` with asset `blah.png` will get loaded as `namespaces/a3d..14f/f12..d3c.css` and `namespaces/a3d..14f/blah.png`. allowing the css file to load `blah.png` by a relative url rather then compute the generated url with get_url_mappings().
+/// The namespace folder's name will change if any of the assets change. (excluding parent assets)
+/datum/asset/simple/namespaced
+ _abstract = /datum/asset/simple/namespaced
+ /// parents - list of the parent asset or assets (in name = file assoicated format) for this namespace.
+ /// parent assets must be referenced by their generated url, but if an update changes a parent asset, it won't change the namespace's identity.
+ var/list/parents = list()
+
+/datum/asset/simple/namespaced/register()
+ if (legacy)
+ assets |= parents
+ var/list/hashlist = list()
+ var/list/sorted_assets = sortList(assets)
+
+ for (var/asset_name in sorted_assets)
+ var/datum/asset_cache_item/ACI = new(asset_name, sorted_assets[asset_name])
+ if (!ACI?.hash)
+ log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
+ continue
+ hashlist += ACI.hash
+ sorted_assets[asset_name] = ACI
+ var/namespace = md5(hashlist.Join())
+
+ for (var/asset_name in parents)
+ var/datum/asset_cache_item/ACI = new(asset_name, parents[asset_name])
+ if (!ACI?.hash)
+ log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
+ continue
+ ACI.namespace_parent = TRUE
+ sorted_assets[asset_name] = ACI
+
+ for (var/asset_name in sorted_assets)
+ var/datum/asset_cache_item/ACI = sorted_assets[asset_name]
+ if (!ACI?.hash)
+ log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
+ continue
+ ACI.namespace = namespace
+
+ assets = sorted_assets
+ ..()
+
+/// Get a html string that will load a html asset.
+/// Needed because byond doesn't allow you to browse() to a url.
+/datum/asset/simple/namespaced/proc/get_htmlloader(filename)
+ return url2htmlloader(SSassets.transport.get_asset_url(filename, assets[filename]))
+
+/// A subtype to generate a JSON file from a list
+/datum/asset/json
+ _abstract = /datum/asset/json
+ /// The filename, will be suffixed with ".json"
+ var/name
+
+/datum/asset/json/send(client)
+ return SSassets.transport.send_assets(client, "[name].json")
+
+/datum/asset/json/get_url_mappings()
+ return list(
+ "[name].json" = SSassets.transport.get_asset_url("[name].json"),
+ )
+
+/datum/asset/json/register()
+ var/filename = "data/[name].json"
+ fdel(filename)
+ text2file(json_encode(generate()), filename)
+ SSassets.transport.register_asset("[name].json", fcopy_rsc(filename))
+ fdel(filename)
+
+/// Returns the data that will be JSON encoded
+/datum/asset/json/proc/generate()
+ SHOULD_CALL_PARENT(FALSE)
+ CRASH("generate() not implemented for [type]!")
+#undef ASSET_CROSS_ROUND_CACHE_DIRECTORY
diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm
index acbd82a3487..8735584dea5 100644
--- a/code/modules/asset_cache/asset_list_items.dm
+++ b/code/modules/asset_cache/asset_list_items.dm
@@ -258,29 +258,26 @@
/datum/asset/spritesheet/pipes
name = "pipes"
-/datum/asset/spritesheet/pipes/register()
+/datum/asset/spritesheet/pipes/create_spritesheets()
for(var/each in list('icons/obj/pipe-item.dmi', 'icons/obj/pipes/disposal.dmi'))
InsertAll("", each, global.alldirs)
- ..()
//VOREStation Add
/datum/asset/spritesheet/vore
name = "vore"
-/datum/asset/spritesheet/vore/register()
+/datum/asset/spritesheet/vore/create_spritesheets()
var/icon/downscaled = icon('icons/mob/screen_full_vore.dmi')
downscaled.Scale(240, 240)
InsertAll("", downscaled)
- ..()
/datum/asset/spritesheet/vore_colorized //This should be getting loaded in the TGUI vore panel but the game refuses to do so, for some reason. It only loads the vore spritesheet.
name = "colorizedvore"
-/datum/asset/spritesheet/vore_colorized/register()
+/datum/asset/spritesheet/vore_colorized/create_spritesheets()
var/icon/downscaledVC = icon('icons/mob/screen_full_colorized_vore.dmi')
downscaledVC.Scale(240, 240)
InsertAll("", downscaledVC)
- ..()
//VOREStation Add End
@@ -346,7 +343,7 @@
/datum/asset/spritesheet/vending
name = "vending"
-/datum/asset/spritesheet/vending/register()
+/datum/asset/spritesheet/vending/create_spritesheets()
populate_vending_products()
for(var/atom/item as anything in GLOB.vending_products)
if(!ispath(item, /atom))
@@ -384,7 +381,6 @@
var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
Insert(imgid, I)
- return ..()
// this is cursed but necessary or else vending product icons can be missing
// basically, if there's any vending machines that aren't already mapped in, our register() will not know
@@ -442,10 +438,10 @@
assets["bottle-[i].png"] = icon('icons/obj/chemical.dmi', "bottle-[i]")
for(var/asset_name in assets)
- register_asset(asset_name, assets[asset_name])
+ SSassets.transport.register_asset(asset_name, assets[asset_name])
/datum/asset/chem_master/send(client)
- send_asset_list(client, assets, verify)
+ SSassets.transport.send_assets(client, assets, verify)
//Cloning pod sprites for UIs
/datum/asset/cloning
@@ -457,10 +453,10 @@
assets["pod_cloning.gif"] = icon('icons/obj/cloning.dmi', "pod_cloning")
assets["pod_mess.gif"] = icon('icons/obj/cloning.dmi', "pod_mess")
for(var/asset_name in assets)
- register_asset(asset_name, assets[asset_name])
+ SSassets.transport.register_asset(asset_name, assets[asset_name])
/datum/asset/cloning/send(client)
- send_asset_list(client, assets, verify)
+ SSassets.transport.send_assets(client, assets, verify)
// VOREStation Add
/datum/asset/cloning/resleeving
@@ -471,15 +467,14 @@
assets["synthprinter.gif"] = icon('icons/obj/machines/synthpod.dmi', "pod_0")
assets["synthprinter_working.gif"] = icon('icons/obj/machines/synthpod.dmi', "pod_1")
for(var/asset_name in assets)
- register_asset(asset_name, assets[asset_name])
+ SSassets.transport.register_asset(asset_name, assets[asset_name])
// VOREStation Add End
/datum/asset/spritesheet/sheetmaterials
name = "sheetmaterials"
-/datum/asset/spritesheet/sheetmaterials/register()
+/datum/asset/spritesheet/sheetmaterials/create_spritesheets()
InsertAll("", 'icons/obj/stacks.dmi')
- ..()
// Nanomaps
/datum/asset/simple/nanomaps
diff --git a/code/modules/asset_cache/assets/chat.dm b/code/modules/asset_cache/assets/chat.dm
index 328beaca9d0..0853ea542c7 100644
--- a/code/modules/asset_cache/assets/chat.dm
+++ b/code/modules/asset_cache/assets/chat.dm
@@ -1,2 +1,6 @@
/datum/asset/spritesheet/chat
name = "chat"
+
+/datum/asset/spritesheet/chat/create_spritesheets()
+ //honk
+ //This function has to be overridden otherwise it will generate runtimes
diff --git a/code/modules/asset_cache/assets/fontawesome.dm b/code/modules/asset_cache/assets/fontawesome.dm
index 72af739f4e1..6f5441ab42d 100644
--- a/code/modules/asset_cache/assets/fontawesome.dm
+++ b/code/modules/asset_cache/assets/fontawesome.dm
@@ -1,9 +1,8 @@
-/datum/asset/simple/fontawesome
+/datum/asset/simple/namespaced/fontawesome
assets = list(
- "fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot',
- "fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff',
- "fa-solid-900.eot" = 'html/font-awesome/webfonts/fa-solid-900.eot',
- "fa-solid-900.woff" = 'html/font-awesome/webfonts/fa-solid-900.woff',
- "font-awesome.css" = 'html/font-awesome/css/all.min.css',
- "v4shim.css" = 'html/font-awesome/css/v4-shims.min.css'
+ "fa-regular-400.ttf" = 'html/font-awesome/webfonts/fa-regular-400.ttf',
+ "fa-solid-900.ttf" = 'html/font-awesome/webfonts/fa-solid-900.ttf',
+ "fa-v4compatibility.ttf" = 'html/font-awesome/webfonts/fa-v4compatibility.ttf',
+ "v4shim.css" = 'html/font-awesome/css/v4-shims.min.css',
)
+ parents = list("font-awesome.css" = 'html/font-awesome/css/all.min.css')
diff --git a/code/modules/asset_cache/assets/jquery.dm b/code/modules/asset_cache/assets/jquery.dm
index 2a28293fbb3..b7241fdb61d 100644
--- a/code/modules/asset_cache/assets/jquery.dm
+++ b/code/modules/asset_cache/assets/jquery.dm
@@ -1,4 +1,5 @@
/datum/asset/simple/jquery
+ legacy = TRUE
assets = list(
- "jquery.min.js" = 'code/modules/tooltip/jquery.min.js',
+ "jquery.min.js" = 'html/jquery/jquery.min.js',
)
diff --git a/code/modules/asset_cache/assets/tgfont.dm b/code/modules/asset_cache/assets/tgfont.dm
index 48db3e4e676..1d9df4282c8 100644
--- a/code/modules/asset_cache/assets/tgfont.dm
+++ b/code/modules/asset_cache/assets/tgfont.dm
@@ -1,6 +1,8 @@
-/datum/asset/simple/tgfont
+/datum/asset/simple/namespaced/tgfont
assets = list(
"tgfont.eot" = file("tgui/packages/tgfont/dist/tgfont.eot"),
"tgfont.woff2" = file("tgui/packages/tgfont/dist/tgfont.woff2"),
+ )
+ parents = list(
"tgfont.css" = file("tgui/packages/tgfont/dist/tgfont.css"),
)
diff --git a/code/modules/asset_cache/readme.md b/code/modules/asset_cache/readme.md
new file mode 100644
index 00000000000..82e6bea896c
--- /dev/null
+++ b/code/modules/asset_cache/readme.md
@@ -0,0 +1,37 @@
+# Asset cache system
+
+## Framework for managing browser assets (javascript,css,images,etc)
+
+This manages getting the asset to the client without doing unneeded re-sends, as well as utilizing any configured cdns.
+
+There are two frameworks for using this system:
+
+### Asset datum:
+
+Make a datum in asset_list_items.dm with your browser assets for your thing.
+
+Checkout asset_list.dm for the helper subclasses
+
+The `simple` subclass will most likely be of use for most cases.
+
+Call get_asset_datum() with the type of the datum you created to get your asset cache datum
+
+Call .send(client|usr) on that datum to send the asset to the client. Depending on the asset transport this may or may not block.
+
+Call .get_url_mappings() to get an associated list with the urls your assets can be found at.
+
+### Manual backend:
+
+See the documentation for `/datum/asset_transport` for the backend api the asset datums utilize.
+
+The global variable `SSassets.transport` contains the currently configured transport.
+
+
+
+### Notes:
+
+Because byond browse() calls use non-blocking queues, if your code uses output() (which bypasses all of these queues) to invoke javascript functions you will need to first have the javascript announce to the server it has loaded before trying to invoke js functions.
+
+To make your code work with any CDNs configured by the server, you must make sure assets are referenced from the url returned by `get_url_mappings()` or by asset_transport's `get_asset_url()`. (TGUI also has helpers for this.) If this can not be easily done, you can bypass the cdn using legacy assets, see the simple asset datum for details.
+
+CSS files that use url() can be made to use the CDN without needing to rewrite all url() calls in code by using the namespaced helper datum. See the documentation for `/datum/asset/simple/namespaced` for details.
diff --git a/code/modules/asset_cache/transports/asset_transport.dm b/code/modules/asset_cache/transports/asset_transport.dm
new file mode 100644
index 00000000000..5be975a385e
--- /dev/null
+++ b/code/modules/asset_cache/transports/asset_transport.dm
@@ -0,0 +1,162 @@
+/// When sending mutiple assets, how many before we give the client a quaint little sending resources message
+#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
+
+/// Base browse_rsc asset transport
+/datum/asset_transport
+ var/name = "Simple browse_rsc asset transport"
+ var/static/list/preload
+ /// Don't mutate the filename of assets when sending via browse_rsc.
+ /// This is to make it easier to debug issues with assets, and allow server operators to bypass issues that make it to production.
+ /// If turning this on fixes asset issues, something isn't using get_asset_url and the asset isn't marked legacy, fix one of those.
+ var/dont_mutate_filenames = FALSE
+
+/// Called when the transport is loaded by the config controller, not called on the default transport unless it gets loaded by a config change.
+/datum/asset_transport/proc/Load()
+ if (config.asset_simple_preload)
+ for(var/client/C in GLOB.clients)
+ addtimer(CALLBACK(src, PROC_REF(send_assets_slow), C, preload), 1 SECONDS)
+
+/// Initialize - Called when SSassets initializes.
+/datum/asset_transport/proc/Initialize(list/assets)
+ preload = assets.Copy()
+ if (!config.asset_simple_preload)
+ return
+ for(var/client/C in GLOB.clients)
+ addtimer(CALLBACK(src, PROC_REF(send_assets_slow), C, preload), 1 SECONDS)
+
+
+/**
+ * Register a browser asset with the asset cache system.
+ * returns a /datum/asset_cache_item.
+ * mutiple calls to register the same asset under the same asset_name return the same datum.
+ *
+ * Arguments:
+ * * asset_name - the identifier of the asset.
+ * * asset - the actual asset file (or an asset_cache_item datum).
+ * * file_hash - optional, a hash of the contents of the asset files contents. used so asset_cache_item doesnt have to hash it again
+ * * dmi_file_path - optional, means that the given asset is from the rsc and thus we dont need to do some expensive operations
+ */
+/datum/asset_transport/proc/register_asset(asset_name, asset, file_hash, dmi_file_path)
+ var/datum/asset_cache_item/ACI = asset
+ if (!istype(ACI))
+ ACI = new(asset_name, asset, file_hash, dmi_file_path)
+ if (!ACI || !ACI.hash)
+ CRASH("ERROR: Invalid asset: [asset_name]:[asset]:[ACI]")
+ if (SSassets.cache[asset_name])
+ var/datum/asset_cache_item/OACI = SSassets.cache[asset_name]
+ OACI.legacy = ACI.legacy = (ACI.legacy|OACI.legacy)
+ OACI.namespace_parent = ACI.namespace_parent = (ACI.namespace_parent | OACI.namespace_parent)
+ OACI.namespace = OACI.namespace || ACI.namespace
+ if (OACI.hash != ACI.hash)
+ var/error_msg = "ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset hash: [OACI.hash] new asset hash:[ACI.hash]"
+ stack_trace(error_msg)
+ log_asset(error_msg)
+ else
+ if (length(ACI.namespace))
+ return ACI
+ return OACI
+
+ SSassets.cache[asset_name] = ACI
+ return ACI
+
+
+/// Returns a url for a given asset.
+/// asset_name - Name of the asset.
+/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name
+/datum/asset_transport/proc/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item)
+ if (!istype(asset_cache_item))
+ asset_cache_item = SSassets.cache[asset_name]
+ // To ensure code that breaks on cdns breaks in local testing, we only
+ // use the normal filename on legacy assets and name space assets.
+ var/keep_local_name = dont_mutate_filenames \
+ || asset_cache_item.legacy \
+ || asset_cache_item.keep_local_name \
+ || (asset_cache_item.namespace && !asset_cache_item.namespace_parent)
+ if (keep_local_name)
+ return url_encode(asset_cache_item.name)
+ return url_encode("asset.[asset_cache_item.hash][asset_cache_item.ext]")
+
+
+/// Sends a list of browser assets to a client
+/// client - a client or mob
+/// asset_list - A list of asset filenames to be sent to the client. Can optionally be assoicated with the asset's asset_cache_item datum.
+/// Returns TRUE if any assets were sent.
+/datum/asset_transport/proc/send_assets(client/client, list/asset_list)
+ if (!istype(client))
+ if (ismob(client))
+ var/mob/M = client
+ if (M.client)
+ client = M.client
+ else //no stacktrace because this will mainly happen because the client went away
+ return
+ else
+ CRASH("Invalid argument: client: `[client]`")
+ if (!islist(asset_list))
+ asset_list = list(asset_list)
+ var/list/unreceived = list()
+
+ for (var/asset_name in asset_list)
+ var/datum/asset_cache_item/ACI = asset_list[asset_name]
+ if (!istype(ACI) && !(ACI = SSassets.cache[asset_name]))
+ log_asset("ERROR: can't send asset `[asset_name]`: unregistered or invalid state: `[ACI]`")
+ continue
+ var/asset_file = ACI.resource
+ if (!asset_file)
+ log_asset("ERROR: can't send asset `[asset_name]`: invalid registered resource: `[ACI.resource]`")
+ continue
+
+ var/asset_hash = ACI.hash
+ var/new_asset_name = asset_name
+ var/keep_local_name = dont_mutate_filenames \
+ || ACI.legacy \
+ || ACI.keep_local_name \
+ || (ACI.namespace && !ACI.namespace_parent)
+ if (!keep_local_name)
+ new_asset_name = "asset.[ACI.hash][ACI.ext]"
+ if (client.sent_assets[new_asset_name] == asset_hash)
+ /*if (GLOB.Debug2)
+ log_asset("DEBUG: Skipping send of `[asset_name]` (as `[new_asset_name]`) for `[client]` because it already exists in the client's sent_assets list")*/
+ continue
+ unreceived[asset_name] = ACI
+
+ if (unreceived.len)
+ if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
+ to_chat(client, "Sending Resources...")
+
+ for (var/asset_name in unreceived)
+ var/new_asset_name = asset_name
+ var/datum/asset_cache_item/ACI = unreceived[asset_name]
+ var/keep_local_name = dont_mutate_filenames \
+ || ACI.legacy \
+ || ACI.keep_local_name \
+ || (ACI.namespace && !ACI.namespace_parent)
+ if (!keep_local_name)
+ new_asset_name = "asset.[ACI.hash][ACI.ext]"
+ log_asset("Sending asset `[asset_name]` to client `[client]` as `[new_asset_name]`")
+ client << browse_rsc(ACI.resource, new_asset_name)
+
+ client.sent_assets[new_asset_name] = ACI.hash
+
+ addtimer(CALLBACK(client, TYPE_PROC_REF(/client, asset_cache_update_json)), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
+ return TRUE
+ return FALSE
+
+
+/// Precache files without clogging up the browse() queue, used for passively sending files on connection start.
+/datum/asset_transport/proc/send_assets_slow(client/client, list/files, filerate = 6)
+ var/startingfilerate = filerate
+ for (var/file in files)
+ if (!client)
+ break
+ if (send_assets(client, file))
+ if (!(--filerate))
+ filerate = startingfilerate
+ client.browse_queue_flush()
+ stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
+
+/// Check the config is valid to load this transport
+/// Returns TRUE or FALSE
+/datum/asset_transport/proc/validate_config(log = TRUE)
+ return TRUE
+
+#undef ASSET_CACHE_TELL_CLIENT_AMOUNT
diff --git a/code/modules/asset_cache/transports/webroot_transport.dm b/code/modules/asset_cache/transports/webroot_transport.dm
new file mode 100644
index 00000000000..9ee8768efd4
--- /dev/null
+++ b/code/modules/asset_cache/transports/webroot_transport.dm
@@ -0,0 +1,87 @@
+/// CDN Webroot asset transport.
+/datum/asset_transport/webroot
+ name = "CDN Webroot asset transport"
+
+/datum/asset_transport/webroot/Load()
+ if (validate_config(log = FALSE))
+ load_existing_assets()
+
+/// Processes thru any assets that were registered before we were loaded as a transport.
+/datum/asset_transport/webroot/proc/load_existing_assets()
+ for (var/asset_name in SSassets.cache)
+ var/datum/asset_cache_item/ACI = SSassets.cache[asset_name]
+ save_asset_to_webroot(ACI)
+
+/// Register a browser asset with the asset cache system
+/// We also save it to the CDN webroot at this step instead of waiting for send_assets()
+/// asset_name - the identifier of the asset
+/// asset - the actual asset file or an asset_cache_item datum.
+/datum/asset_transport/webroot/register_asset(asset_name, asset)
+ . = ..()
+ var/datum/asset_cache_item/ACI = .
+
+ if (istype(ACI) && ACI.hash)
+ save_asset_to_webroot(ACI)
+
+/// Saves the asset to the webroot taking into account namespaces and hashes.
+/datum/asset_transport/webroot/proc/save_asset_to_webroot(datum/asset_cache_item/ACI)
+ var/webroot = config.asset_cdn_webroot
+ var/newpath = "[webroot][get_asset_suffex(ACI)]"
+ if (fexists(newpath))
+ return
+ if (fexists("[newpath].gz")) //its a common pattern in webhosting to save gzip'ed versions of text files and let the webserver serve them up as gzip compressed normal files, sometimes without keeping the original version.
+ return
+ return fcopy(ACI.resource, newpath)
+
+/// Returns a url for a given asset.
+/// asset_name - Name of the asset.
+/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name
+/datum/asset_transport/webroot/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item)
+ if (!istype(asset_cache_item))
+ asset_cache_item = SSassets.cache[asset_name]
+ var/url = config.asset_cdn_url //config loading will handle making sure this ends in a /
+ return "[url][get_asset_suffex(asset_cache_item)]"
+
+/datum/asset_transport/webroot/proc/get_asset_suffex(datum/asset_cache_item/asset_cache_item)
+ var/base = "[copytext(asset_cache_item.hash, 1, 3)]/"
+ var/filename = "asset.[asset_cache_item.hash][asset_cache_item.ext]"
+ if (length(asset_cache_item.namespace))
+ base = "namespaces/[copytext(asset_cache_item.namespace, 1, 3)]/[asset_cache_item.namespace]/"
+ if (!asset_cache_item.namespace_parent)
+ filename = "[asset_cache_item.name]"
+ return base + filename
+
+
+/// webroot asset sending - does nothing unless passed legacy assets
+/datum/asset_transport/webroot/send_assets(client/client, list/asset_list)
+ . = FALSE
+ var/list/legacy_assets = list()
+ if (!islist(asset_list))
+ asset_list = list(asset_list)
+ for (var/asset_name in asset_list)
+ var/datum/asset_cache_item/ACI = asset_list[asset_name]
+ if (!istype(ACI))
+ ACI = SSassets.cache[asset_name]
+ if (!ACI)
+ legacy_assets += asset_name //pass it on to base send_assets so it can output an error
+ continue
+ if (ACI.legacy)
+ legacy_assets[asset_name] = ACI
+ if (length(legacy_assets))
+ . = ..(client, legacy_assets)
+
+
+/// webroot slow asset sending - does nothing.
+/datum/asset_transport/webroot/send_assets_slow(client/client, list/files, filerate)
+ return FALSE
+
+/datum/asset_transport/webroot/validate_config(log = TRUE)
+ if (!config.asset_cdn_url)
+ if (log)
+ log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_URL")
+ return FALSE
+ if (!config.asset_cdn_webroot)
+ if (log)
+ log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_WEBROOT")
+ return FALSE
+ return TRUE
diff --git a/code/modules/asset_cache/validate_assets.html b/code/modules/asset_cache/validate_assets.html
index b27a266c00d..78bcbb92a1a 100644
--- a/code/modules/asset_cache/validate_assets.html
+++ b/code/modules/asset_cache/validate_assets.html
@@ -26,4 +26,4 @@