This commit is contained in:
zerothebigboy
2021-09-11 23:04:48 -04:00
156 changed files with 8131 additions and 4742 deletions
+2 -1
View File
@@ -437,8 +437,9 @@
#define COMPONENT_BLOCK_SHARPEN_BLOCKED 2
#define COMPONENT_BLOCK_SHARPEN_ALREADY 4
#define COMPONENT_BLOCK_SHARPEN_MAXED 8
#define COMSIG_ITEM_MICROWAVE_ACT "microwave_act" //called on item when microwaved (): (obj/machinery/microwave/M)
#define COMSIG_ITEM_MICROWAVE_ACT "microwave_act" //called on item when microwaved (): (obj/machinery/microwave/M)
#define COMSIG_ITEM_WORN_OVERLAYS "item_worn_overlays" //from base of obj/item/worn_overlays(): (isinhands, icon_file, used_state, style_flags, list/overlays)
#define COMSIG_ARMOR_PLATED "armor_plated" //called when an armor plate is successfully applied to an object
// THE FOLLOWING TWO BLOCKS SHOULD RETURN BLOCK FLAGS AS DEFINED IN __DEFINES/combat.dm!
#define COMSIG_ITEM_CHECK_BLOCK "check_block" //from base of obj/item/check_block(): (mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
#define COMSIG_ITEM_RUN_BLOCK "run_block" //from base of obj/item/run_block(): (mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
+28 -14
View File
@@ -1,30 +1,44 @@
//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
/// `qdel` should let the object live after calling [/atom/proc/Destroy].
#define QDEL_HINT_LETMELIVE 1
/// Functionally the same as the above. `qdel` should assume the object will gc on its own, and not check it.
#define QDEL_HINT_IWILLGC 2
/// Qdel should assume this object won't GC, and queue a hard delete using a hard reference.
#define QDEL_HINT_HARDDEL 3
// Qdel should assume this object won't gc, and hard delete it posthaste.
#define QDEL_HINT_HARDDEL_NOW 4
#define QDEL_HINT_QUEUE 0 //qdel should queue the object for deletion.
#define QDEL_HINT_LETMELIVE 1 //qdel should let the object live after calling destory.
#define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it.
#define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference.
#define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste.
#ifdef LEGACY_REFERENCE_TRACKING
/** If LEGACY_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.
#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.
*/
#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_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.
#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
// Defines for the time left for an item to get its reference cleaned
#define GC_FILTER_QUEUE 5 MINUTES
#define GC_DEL_QUEUE 10 SECONDS
#define QDELING(X) (X.gc_destroyed)
#define QDELETED(X) (!X || QDELING(X))
#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
+13 -2
View File
@@ -38,6 +38,9 @@
#define RUST_G (__rust_g || __detect_rust_g())
#endif
/// Gets the version of rust_g
/proc/rustg_get_version() return call(RUST_G, "get_version")()
/**
* This proc generates a cellular automata noise grid which can be used in procedural generation methods.
*
@@ -77,8 +80,8 @@
#define RUSTG_HTTP_METHOD_PATCH "patch"
#define RUSTG_HTTP_METHOD_HEAD "head"
#define RUSTG_HTTP_METHOD_POST "post"
#define rustg_http_request_blocking(method, url, body, headers) call(RUST_G, "http_request_blocking")(method, url, body, headers)
#define rustg_http_request_async(method, url, body, headers) call(RUST_G, "http_request_async")(method, url, body, headers)
#define rustg_http_request_blocking(method, url, body, headers, options) call(RUST_G, "http_request_blocking")(method, url, body, headers, options)
#define rustg_http_request_async(method, url, body, headers, options) call(RUST_G, "http_request_async")(method, url, body, headers, options)
#define rustg_http_check_request(req_id) call(RUST_G, "http_check_request")(req_id)
#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET"
@@ -99,3 +102,11 @@
#define rustg_sql_disconnect_pool(handle) call(RUST_G, "sql_disconnect_pool")(handle)
#define rustg_sql_check_query(job_id) call(RUST_G, "sql_check_query")("[job_id]")
#define rustg_url_encode(text) call(RUST_G, "url_encode")(text)
#define rustg_url_decode(text) call(RUST_G, "url_decode")(text)
#ifdef RUSTG_OVERRIDE_BUILTINS
#define url_encode(text) rustg_url_encode(text)
#define url_decode(text) rustg_url_decode(text)
#endif
+3
View File
@@ -0,0 +1,3 @@
// RUSTG_OVERRIDE_BUILTINS is not used since the file APIs don't work well over Linux.
#define url_encode(text) rustg_url_encode("[text]")
#define url_decode(text) rustg_url_decode("[text]")
+6
View File
@@ -49,10 +49,16 @@ When using time2text(), please use "DDD" to find the weekday. Refrain from using
#define TICKS *world.tick_lag
#define MILLISECONDS * 0.01
#define DS2TICKS(DS) ((DS)/world.tick_lag)
#define TICKS2DS(T) ((T) TICKS)
#define MS2DS(T) ((T) MILLISECONDS)
#define DS2MS(T) ((T) * 100)
#define GAMETIMESTAMP(format, wtime) time2text(wtime, format)
#define WORLDTIME2TEXT(format) GAMETIMESTAMP(format, world.time)
#define WORLDTIMEOFDAY2TEXT(format) GAMETIMESTAMP(format, world.timeofday)
+4
View File
@@ -343,3 +343,7 @@
#define STICKY_NODROP "sticky-nodrop" //sticky nodrop sounds like a bad soundcloud rapper's name
#define TRAIT_SACRIFICED "sacrificed" //Makes sure that people cant be cult sacrificed twice.
#define TRAIT_SPACEWALK "spacewalk"
/// obtained from mapping helper
#define MAPPING_HELPER_TRAIT "mapping-helper"
+10
View File
@@ -1,9 +1,12 @@
//wrapper macros for easier grepping
#define DIRECT_OUTPUT(A, B) A << B
#define DIRECT_INPUT(A, B) A >> B
#define SEND_IMAGE(target, image) DIRECT_OUTPUT(target, image)
#define SEND_SOUND(target, sound) DIRECT_OUTPUT(target, sound)
#define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text)
#define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text)
#define READ_FILE(file, text) DIRECT_INPUT(file, text)
#ifdef EXTOOLS_LOGGING
// proc hooked, so we can just put in standard TRUE and FALSE
#define WRITE_LOG(log, text) extools_log_write(log,text,TRUE)
@@ -13,6 +16,7 @@
#define WRITE_LOG(log, text) rustg_log_write(log, text, "true")
#define WRITE_LOG_NO_FORMAT(log, text) rustg_log_write(log, text, "false")
#endif
//print a warning message to world.log
#define WARNING(MSG) warning("[MSG] in [__FILE__] at line [__LINE__] src: [UNLINT(src)] usr: [usr].")
/proc/warning(msg)
@@ -38,6 +42,12 @@
SEND_TEXT(world.log, text)
#endif
#ifdef REFERENCE_TRACKING_LOG
#define log_reftracker(msg) log_world("## REF SEARCH [msg]")
#else
#define log_reftracker(msg)
#endif
/* Items with ADMINPRIVATE prefixed are stripped from public logs. */
/proc/log_admin(text)
+6
View File
@@ -78,6 +78,12 @@
var/datum/emote/E = new path()
E.emote_list[E.key] = E
// Hair Gradients - Initialise all /datum/sprite_accessory/hair_gradient into an list indexed by gradient-style name
for(var/path in subtypesof(/datum/sprite_accessory/hair_gradient))
var/datum/sprite_accessory/hair_gradient/H = new path()
GLOB.hair_gradients_list[H.name] = H
// Keybindings
init_keybindings()
//Uplink Items
+13 -13
View File
@@ -218,20 +218,14 @@
to_chat(world, "<BR><BR><BR><span class='big bold'>The round has ended.</span>")
log_game("The round has ended.")
if(LAZYLEN(GLOB.round_end_notifiees))
world.TgsTargetedChatBroadcast("[GLOB.round_end_notifiees.Join(", ")] the round has ended.", FALSE)
CONFIG_SET(flag/suicide_allowed,TRUE) // EORG suicides allowed
for(var/I in round_end_events)
var/datum/callback/cb = I
cb.InvokeAsync()
LAZYCLEARLIST(round_end_events)
for(var/client/C in GLOB.clients)
if(!C.credits)
C.RollCredits()
C.playtitlemusic(40)
CONFIG_SET(flag/suicide_allowed,TRUE) // EORG suicides allowed
var/speed_round = FALSE
if(world.time - SSticker.round_start_time <= 300 SECONDS)
speed_round = TRUE
@@ -264,12 +258,15 @@
send2adminchat("Server", "A round of [mode.name] just ended[mode_result == "undefined" ? "." : " with a [mode_result]."] Survival rate: [survival_rate]")
if(LAZYLEN(GLOB.round_end_notifiees))
world.TgsTargetedChatBroadcast("[GLOB.round_end_notifiees.Join(", ")] the round has ended.", FALSE)
if(length(CONFIG_GET(keyed_list/cross_server)))
send_news_report()
//tell the nice people on discord what went on before the salt cannon happens.
world.TgsTargetedChatBroadcast("The current round has ended. Please standby for your shift interlude Nanotrasen News Network's report!", FALSE)
world.TgsTargetedChatBroadcast(send_news_report(),FALSE)
world.TgsTargetedChatBroadcast(send_news_report(), FALSE)
CHECK_TICK
@@ -543,21 +540,24 @@
///Generate a report for how much money is on station, as well as the richest crewmember on the station.
/datum/controller/subsystem/ticker/proc/market_report()
var/list/parts = list()
parts += "<span class='header'>Station Economic Summary:</span>"
///This is the richest account on station at roundend.
var/datum/bank_account/mr_moneybags
///This is the station's total wealth at the end of the round.
var/station_vault = 0
///How many players joined the round.
var/total_players = GLOB.joined_player_list.len
var/list/typecache_bank = typecacheof(list(/datum/bank_account/department, /datum/bank_account/remote))
for(var/datum/bank_account/current_acc in SSeconomy.generated_accounts)
var/static/list/typecache_bank = typecacheof(list(/datum/bank_account/department, /datum/bank_account/remote))
for(var/i in SSeconomy.generated_accounts)
var/datum/bank_account/current_acc = SSeconomy.generated_accounts[i]
if(typecache_bank[current_acc.type])
continue
station_vault += current_acc.account_balance
if(!mr_moneybags || mr_moneybags.account_balance < current_acc.account_balance)
mr_moneybags = current_acc
parts += "<div class='panel stationborder'>There were [station_vault] credits collected by crew this shift.<br>"
parts += "<div class='panel stationborder'><span class='header'>Station Economic Summary:</span><br>"
parts += "<b>General Statistics:</b><br>"
parts += "There were [station_vault] credits collected by crew this shift.<br>"
if(total_players > 0)
parts += "An average of [station_vault/total_players] credits were collected.<br>"
// log_econ("Roundend credit total: [station_vault] credits. Average Credits: [station_vault/total_players]")
+47 -23
View File
@@ -1,7 +1,7 @@
//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this
//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this
//uncommented, but not visible in the release version)
//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed.
//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed.
// Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error
#ifdef DEBUG
@@ -11,34 +11,44 @@
#ifdef TESTING
#define DATUMVAR_DEBUGGING_MODE
/*
* Enables extools-powered reference tracking system, letting you see what is referencing objects that refuse to hard delete.
*
* * Requires TESTING to be defined to work.
*/
///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while
//#define REFERENCE_TRACKING
#ifdef REFERENCE_TRACKING
///Method of tracking references without using extools. Slower, kept to avoid over-reliance on extools.
//#define LEGACY_REFERENCE_TRACKING
#ifdef LEGACY_REFERENCE_TRACKING
///alternate to reftracking, extool variant
//#define EXTOOLS_REFERENCE_TRACKING
///Use the legacy reference on things hard deleting by default.
///Should we be logging our findings or not
#define REFERENCE_TRACKING_LOG
///Used for doing dry runs of the reference finder, to test for feature completeness
//#define REFERENCE_TRACKING_DEBUG
///Run a lookup on things hard deleting by default.
//#define GC_FAILURE_HARD_LOOKUP
#ifdef GC_FAILURE_HARD_LOOKUP
#define FIND_REF_NO_CHECK_TICK
#endif //ifdef GC_FAILURE_HARD_LOOKUP
#endif //ifdef LEGACY_REFERENCE_TRACKING
#endif //ifdef REFERENCE_TRACKING
//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
/*
* Enables debug messages for every single reaction step. This is 1 message per 0.5s for a SINGLE reaction. Useful for tracking down bugs/asking me for help in the main reaction handiler (equilibrium.dm).
*
* * Requires TESTING to be defined to work.
*/
//#define REAGENTS_TESTING
// #define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
// #define TRACK_MAX_SHARE //Allows max share tracking, for use in the atmos debugging ui
#endif //ifdef TESTING
//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER
#ifndef PRELOAD_RSC //set to:
#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour;
#endif // 1 to use the default behaviour;
// 2 for preloading absolutely everything;
//#define UNIT_TESTS //If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between
#ifndef PRELOAD_RSC //set to:
#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour;
#endif // 1 to use the default behaviour;
// 2 for preloading absolutely everything;
#ifdef LOWMEMORYMODE
#define FORCE_MAP "_maps/runtimestation.json"
@@ -47,7 +57,7 @@
//Update this whenever you need to take advantage of more recent byond features
#define MIN_COMPILER_VERSION 513
#define MIN_COMPILER_BUILD 1514
#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD
#if (DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD) && !defined(SPACEMAN_DMM)
//Don't forget to update this part
#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update.
#error You need version 513.1514 or higher
@@ -58,10 +68,6 @@
#warn compiling in TESTING mode. testing() debug messages will be visible.
#endif
#ifdef GC_FAILURE_HARD_LOOKUP
#define FIND_REF_NO_CHECK_TICK
#endif
#ifdef CIBUILDING
#define UNIT_TESTS
#endif
@@ -70,6 +76,24 @@
#define TESTING
#endif
#if defined(UNIT_TESTS)
//Hard del testing defines
#define REFERENCE_TRACKING
#define REFERENCE_TRACKING_DEBUG
#define FIND_REF_NO_CHECK_TICK
#endif
#ifdef TGS
// TGS performs its own build of dm.exe, but includes a prepended TGS define.
#define CBT
#endif
// A reasonable number of maximum overlays an object needs
// If you think you need more, rethink it
#define MAX_ATOM_OVERLAYS 100
#if !defined(CBT) && !defined(SPACEMAN_DMM)
#warn Building with Dream Maker is no longer supported and will result in errors.
#warn In order to build, run BUILD.bat in the root directory.
#warn Consider switching to VSCode editor instead, where you can press Ctrl+Shift+B to build.
#endif
+1
View File
@@ -6,6 +6,7 @@ GLOBAL_LIST_EMPTY(hair_styles_female_list) //stores only hair names
GLOBAL_LIST_EMPTY(facial_hair_styles_list) //stores /datum/sprite_accessory/facial_hair indexed by name
GLOBAL_LIST_EMPTY(facial_hair_styles_male_list) //stores only hair names
GLOBAL_LIST_EMPTY(facial_hair_styles_female_list) //stores only hair names
GLOBAL_LIST_EMPTY(hair_gradients_list) //stores /datum/sprite_accessory/hair_gradient indexed by name
//Underwear
GLOBAL_LIST_EMPTY_TYPED(underwear_list, /datum/sprite_accessory/underwear/bottom) //stores bottoms indexed by name
GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name
+42
View File
@@ -63,6 +63,8 @@
return ShiftClickOn(A)
if(modifiers["alt"]) // alt and alt-gr (rightalt)
return AltClickOn(A)
if(modifiers["ctrl"] && modifiers["right"]) //CIT CHANGE - right click ctrl for a new form of dropping items
return CtrlRightClickOn(A, params) //CIT CHANGE
if(modifiers["ctrl"])
return CtrlClickOn(A)
@@ -295,6 +297,46 @@
if(!(flags & COMPONENT_DENY_EXAMINATE) && user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE))
user.examinate(src)
/*
Ctrl + Right click
Combat mode feature
Drop item in hand at position.
*/
/atom/proc/CtrlRightClickOn(atom/A, params)
if(isliving(src) && Adjacent(A)) //honestly only humans can do this given it's combat mode but if it's implemented for any other mobs...
var/mob/living/L = src
if(L.incapacitated())
return
var/obj/item/I = L.get_active_held_item()
var/turf/T = get_turf(A)
if(T)
if(I) //drop item at cursor.
if(T.density) //no, you can't use your funny blue cube or red cube to clip into the fucking wall.
return
for(var/atom/C in T.contents) //nor can you clip into a window or a door/false wall that's not open.
if(C.opacity || (((C.flags_1 & PREVENT_CLICK_UNDER_1) > 0) != (istype(C,/obj/machinery/door) && !C.density))) //XOR operation within because doors always have PREVENT_CLICK_UNDER_1 flag enabled. Dumb, I know.
return
if(L.transferItemToLoc(I, T))
var/list/click_params = params2list(params)
//Center the icon where the user clicked. (shamelessly stole code from tables)
if(!click_params || !click_params["icon-x"] || !click_params["icon-y"])
return
//Clamp it so that the icon never moves more than 16 pixels in either direction
I.pixel_x = clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2)
I.pixel_y = clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2)
return TRUE
else if(isitem(A) && L.has_active_hand()) //if they have an open hand they'll rotate the item instead.
var/obj/item/I2 = A
if(!I2.anchored)
var/matrix/ntransform = matrix(I2.transform)
ntransform.Turn(15)
animate(I2, transform = ntransform, time = 2)
return TRUE
else
A.CtrlClick(src)
/*
Ctrl click
For most objects, pull
+1 -1
View File
@@ -43,7 +43,7 @@
INVOKE_ASYNC(aicamera, /obj/item/camera.proc/captureimage, A, usr)
return
var/obj/item/W = get_active_held_item()
var/obj/item/W = get_active_held_item(TRUE)
if(!W && A.Adjacent(src) && (isobj(A) || ismob(A)))
var/atom/movable/C = A
+39 -1
View File
@@ -1,14 +1,25 @@
// Clickable stat() button.
/obj/effect/statclick
name = "Initializing..."
// blocks_emissive = NONE
var/target
INITIALIZE_IMMEDIATE(/obj/effect/statclick)
/obj/effect/statclick/Initialize(mapload, text, target) //Don't port this to Initialize it's too critical
/obj/effect/statclick/Initialize(mapload, text, target)
. = ..()
name = text
src.target = target
if(istype(target, /datum)) //Harddel man bad
RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/cleanup)
/obj/effect/statclick/Destroy()
target = null
return ..()
/obj/effect/statclick/proc/cleanup()
SIGNAL_HANDLER
qdel(src)
/obj/effect/statclick/proc/update(text)
name = text
@@ -51,3 +62,30 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Failsafe Controller")
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
/client/proc/debug_controller()
set category = "Debug"
set name = "Debug Controller"
set desc = "Debug the various periodic loop controllers for the game (be careful!)"
if(!holder)
return
var/list/controllers = list()
var/list/controller_choices = list()
for (var/datum/controller/controller in world)
if (istype(controller, /datum/controller/subsystem))
continue
controllers["[controller] (controller.type)"] = controller //we use an associated list to ensure clients can't hold references to controllers
controller_choices += "[controller] (controller.type)"
var/datum/controller/controller_string = input("Select controller to debug", "Debug Controller") as null|anything in controller_choices
var/datum/controller/controller = controllers[controller_string]
if (!istype(controller))
return
debug_variables(controller)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Failsafe Controller")
message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
@@ -39,7 +39,9 @@
/datum/config_entry/flag/announce_admin_login
/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
/datum/config_entry/string/centcom_source_whitelist
/datum/config_entry/flag/autoadmin // if autoadmin is enabled
protection = CONFIG_ENTRY_LOCKED
@@ -333,3 +333,12 @@
config_entry_value = 0.333
min_val = 0
integer = FALSE
/datum/config_entry/number/hard_deletes_overrun_threshold
integer = FALSE
min_val = 0
default = 0.5
/datum/config_entry/number/hard_deletes_overrun_limit
default = 0
min_val = 0
+95 -14
View File
@@ -15,7 +15,7 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
// The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC.
var/defcon = 5
//the world.time of the last check, so the mc can restart US if we hang.
// (Real friends look out for *eachother*)
// (Real friends look out for *eachother*)
var/lasttick = 0
// Track the MC iteration to make sure its still on track.
@@ -33,6 +33,22 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
/datum/controller/failsafe/Initialize()
set waitfor = FALSE
Failsafe.Loop()
if (!Master || defcon == 0) //Master is gone/not responding and Failsafe just exited its loop
defcon = 3 //Reset defcon level as its used inside the emergency loop
while (defcon > 0)
var/recovery_result = emergency_loop()
if (recovery_result == 1) //Exit emergency loop and delete self if it was able to recover MC
break
else if (defcon == 1) //Exit Failsafe if we weren't able to recover the MC in the last stage
log_game("FailSafe: Failed to recover MC while in emergency state. Failsafe exiting.")
message_admins(span_boldannounce("Failsafe failed criticaly while trying to recreate broken MC. Please manually fix the MC or reboot the server. Failsafe exiting now."))
message_admins(span_boldannounce("You can try manually calling these two procs:."))
message_admins(span_boldannounce("/proc/recover_all_SS_and_recreate_master: Most stuff should still function but expect instability/runtimes/broken stuff."))
message_admins(span_boldannounce("/proc/delete_all_SS_and_recreate_master: Most stuff will be broken but basic stuff like movement and chat should still work."))
else if (recovery_result == -1) //Failed to recreate MC
defcon--
sleep(initial(processing_interval)) //Wait a bit until the next try
if(!QDELETED(src))
qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us
@@ -45,42 +61,56 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
while(running)
lasttick = world.time
if(!Master)
// Replace the missing Master! This should never, ever happen.
new /datum/controller/master()
// Break out of the main loop so we go into emergency state
break
// Only poke it if overrides are not in effect.
if(processing_interval > 0)
if(Master.processing && Master.iteration)
if (defcon > 1 && (!Master.stack_end_detector || !Master.stack_end_detector.check()))
to_chat(GLOB.admins, span_boldannounce("ERROR: The Master Controller code stack has exited unexpectedly, Restarting..."))
defcon = 0
var/rtn = Recreate_MC()
if(rtn > 0)
master_iteration = 0
to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
else if(rtn < 0)
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying."))
// Check if processing is done yet.
if(Master.iteration == master_iteration)
switch(defcon)
if(4,5)
--defcon
if(3)
message_admins("<span class='adminnotice'>Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.</span>")
--defcon
if(2)
to_chat(GLOB.admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.</span>")
--defcon
if(1)
to_chat(GLOB.admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...</span>")
if(3)
message_admins(span_adminnotice("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks."))
--defcon
if(2)
to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks."))
--defcon
if(1)
to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting..."))
--defcon
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, "<span class='adminnotice'>MC restarted successfully</span>")
to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
else if(rtn < 0)
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
to_chat(GLOB.admins, "<span class='boldannounce'>ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.</span>")
to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying."))
//if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again
//no need to handle that specially when defcon 0 can handle it
if(0) //DEFCON 0! (mc failed to restart)
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, "<span class='adminnotice'>MC restarted successfully</span>")
to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
else
defcon = min(defcon + 1,5)
master_iteration = Master.iteration
@@ -92,6 +122,57 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
defcon = 5
sleep(initial(processing_interval))
//Emergency loop used when Master got deleted or the main loop exited while Defcon == 0
//Loop is driven externally so runtimes only cancel the current recovery attempt
/datum/controller/failsafe/proc/emergency_loop()
//The code in this proc should be kept as simple as possible, anything complicated like to_chat might rely on master existing and runtime
//The goal should always be to get a new Master up and running before anything else
. = -1
switch (defcon) //The lower defcon goes the harder we try to fix the MC
if (2 to 3) //Try to normally recreate the MC two times
. = Recreate_MC()
if (1) //Delete the old MC first so we don't transfer any info, in case that caused any issues
del(Master)
. = Recreate_MC()
if (. == 1) //We were able to create a new master
master_iteration = 0
SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set
Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this
to_chat(GLOB.admins, span_adminnotice("Failsafe recovered MC while in emergency state [defcon_pretty()]"))
else
log_game("FailSafe: Failsafe in emergency state and was unable to recreate MC while in defcon state [defcon_pretty()].")
message_admins(span_boldannounce("Failsafe in emergency state and master down, trying to recreate MC while in defcon level [defcon_pretty()] failed."))
///Recreate all SSs which will still cause data survive due to Recover(), the new Master will then find and take them from global.vars
/proc/recover_all_SS_and_recreate_master()
del(Master)
var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
sortTim(subsytem_types, /proc/cmp_subsystem_init)
for(var/I in subsytem_types)
new I
. = Recreate_MC()
if (. == 1) //We were able to create a new master
SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set
Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this
to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after recovering all subsystems!"))
else
message_admins(span_boldannounce("Failed to create new MC!"))
///Delete all existing SS to basically start over
/proc/delete_all_SS_and_recreate_master()
del(Master)
for(var/global_var in global.vars)
if (istype(global.vars[global_var], /datum/controller/subsystem))
del(global.vars[global_var])
. = Recreate_MC()
if (. == 1) //We were able to create a new master
SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set
Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this
to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after deleting and recreating all subsystems!"))
else
message_admins(span_boldannounce("Failed to create new MC!"))
/datum/controller/failsafe/proc/defcon_pretty()
return defcon
+1 -1
View File
@@ -14,7 +14,7 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
var/datum/controller/exclude_these = new
gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
QDEL_IN(exclude_these, 0) //signal logging isn't ready
QDEL_IN(exclude_these, 0) //signal logging isn't ready
log_world("[vars.len - gvars_datum_in_built_vars.len] global variables")
+59 -57
View File
@@ -18,15 +18,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/datum/controller/master
name = "Master"
// Are we processing (higher values increase the processing delay by n ticks)
/// Are we processing (higher values increase the processing delay by n ticks)
var/processing = TRUE
// How many times have we ran
/// How many times have we ran
var/iteration = 0
/// Stack end detector to detect stack overflows that kill the mc's main loop
var/datum/stack_end_detector/stack_end_detector
// world.time of last fire, for tracking lag outside of the mc
/// world.time of last fire, for tracking lag outside of the mc
var/last_run
// List of subsystems to process().
/// List of subsystems to process().
var/list/subsystems
// Vars for keeping track of tick drift.
@@ -34,25 +36,27 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/init_time
var/tickdrift = 0
/// How long is the MC sleeping between runs, read only (set by Loop() based off of anti-tick-contention heuristics)
var/sleep_delta = 1
///Only run ticker subsystems for the next n ticks.
/// Only run ticker subsystems for the next n ticks.
var/skip_ticks = 0
/// makes the mc main loop runtime
var/make_runtime = FALSE
var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
// The type of the last subsystem to be process()'d.
/// The type of the last subsystem to be fire()'d.
var/last_type_processed
var/datum/controller/subsystem/queue_head //Start of queue linked list
var/datum/controller/subsystem/queue_tail //End of queue linked list (used for appending to the list)
var/datum/controller/subsystem/queue_head //!Start of queue linked list
var/datum/controller/subsystem/queue_tail //!End of queue linked list (used for appending to the list)
var/queue_priority_count = 0 //Running total so that we don't have to loop thru the queue each run to split up the tick
var/queue_priority_count_bg = 0 //Same, but for background subsystems
var/map_loading = FALSE //Are we loading in a new map?
var/map_loading = FALSE //!Are we loading in a new map?
var/current_runlevel //for scheduling different subsystems for different stages of the round
var/current_runlevel //!for scheduling different subsystems for different stages of the round
var/sleep_offline_after_initializations = TRUE
var/static/restart_clear = 0
@@ -61,8 +65,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/static/random_seed
//current tick limit, assigned before running a subsystem.
//used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits
///current tick limit, assigned before running a subsystem.
///used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits
var/static/current_ticklimit = TICK_LIMIT_RUNNING
/datum/controller/master/New()
@@ -81,15 +85,27 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/list/_subsystems = list()
subsystems = _subsystems
if (Master != src)
if (istype(Master))
if (istype(Master)) //If there is an existing MC take over his stuff and delete it
Recover()
qdel(Master)
Master = src
else
//Code used for first master on game boot or if existing master got deleted
Master = src
var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
sortTim(subsytem_types, /proc/cmp_subsystem_init)
//Find any abandoned subsystem from the previous master (if there was any)
var/list/existing_subsystems = list()
for(var/global_var in global.vars)
if (istype(global.vars[global_var], /datum/controller/subsystem))
existing_subsystems += global.vars[global_var]
//Either init a new SS or if an existing one was found use that
for(var/I in subsytem_types)
_subsystems += new I
Master = src
var/datum/controller/subsystem/existing_subsystem = locate(I) in existing_subsystems
if (istype(existing_subsystem))
_subsystems += existing_subsystem
else
_subsystems += new I
if(!GLOB)
new /datum/controller/global_vars
@@ -109,7 +125,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
log_world("Shutdown complete")
// Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart,
// -1 if we encountered a runtime trying to recreate it
// -1 if we encountered a runtime trying to recreate it
/proc/Recreate_MC()
. = -1 //so if we runtime, things know we failed
if (world.time < Master.restart_timeout)
@@ -120,7 +136,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/delay = 50 * ++Master.restart_count
Master.restart_timeout = world.time + delay
Master.restart_clear = world.time + (delay * 2)
Master.processing = FALSE //stop ticking this one
if (Master) //Can only do this if master hasn't been deleted
Master.processing = FALSE //stop ticking this one
try
new/datum/controller/master()
catch
@@ -156,22 +173,22 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined."
BadBoy.flags |= SS_NO_FIRE
if(msg)
to_chat(GLOB.admins, "<span class='boldannounce'>[msg]</span>")
to_chat(GLOB.admins, span_boldannounce("[msg]"))
log_world(msg)
if (istype(Master.subsystems))
if(FireHim)
Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one
Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one
subsystems = Master.subsystems
current_runlevel = Master.current_runlevel
StartProcessing(10)
else
to_chat(world, "<span class='boldannounce'>The Master Controller is having some issues, we will need to re-initialize EVERYTHING</span>")
to_chat(world, span_boldannounce("The Master Controller is having some issues, we will need to re-initialize EVERYTHING"))
Initialize(20, TRUE)
// Please don't stuff random bullshit here,
// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize()
// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize()
/datum/controller/master/Initialize(delay, init_sss, tgs_prime)
set waitfor = 0
@@ -181,7 +198,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(init_sss)
init_subtypes(/datum/controller/subsystem, subsystems)
to_chat(world, "<span class='boldannounce'>Initializing subsystems...</span>")
to_chat(world, span_boldannounce("Initializing subsystems..."))
// Sort subsystems by init_order, so they initialize in the correct order.
sortTim(subsystems, /proc/cmp_subsystem_init)
@@ -190,7 +207,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Initialize subsystems.
current_ticklimit = CONFIG_GET(number/tick_limit_mc_init)
for (var/datum/controller/subsystem/SS in subsystems)
if (SS.flags & SS_NO_INIT)
if (SS.flags & SS_NO_INIT || SS.initialized) //Don't init SSs with the correspondig flag or if they already are initialzized
continue
SS.Initialize(REALTIMEOFDAY)
CHECK_TICK
@@ -198,7 +215,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!"
to_chat(world, "<span class='boldannounce'>[msg]</span>")
to_chat(world, span_boldannounce("[msg]"))
log_world(msg)
if (!current_runlevel)
@@ -255,11 +272,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/datum/controller/master/proc/Loop()
. = -1
//Prep the loop (most of this is because we want MC restarts to reset as much state as we can, and because
// local vars rock
// local vars rock
//all this shit is here so that flag edits can be refreshed by restarting the MC. (and for speed)
var/list/tickersubsystems = list()
var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel
var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel
var/timer = world.time
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
@@ -305,8 +322,12 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/error_level = 0
var/sleep_delta = 1
var/list/subsystems_to_check
//the actual loop.
//setup the stack overflow detector
stack_end_detector = new()
var/datum/stack_canary/canary = stack_end_detector.prime_canary()
canary.use_variable()
//the actual loop.
while (1)
tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag)))
var/starting_tick_usage = TICK_USAGE
@@ -317,7 +338,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
//Anti-tick-contention heuristics:
//if there are mutiple sleeping procs running before us hogging the cpu, we have to run later.
// (because sleeps are processed in the order received, longer sleeps are more likely to run first)
// (because sleeps are processed in the order received, longer sleeps are more likely to run first)
if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit.
sleep_delta *= 2
current_ticklimit = TICK_LIMIT_RUNNING * 0.5
@@ -423,6 +444,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
continue
if ((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time)
continue
if (SS.postponed_fires >= 1)
SS.postponed_fires--
SS.update_nextfire()
continue
SS.enqueue()
. = 1
@@ -439,12 +464,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/tick_precentage
var/tick_remaining
var/ran = TRUE //this is right
var/ran_non_ticker = FALSE
var/bg_calc //have we swtiched current_tick_budget to background mode yet?
var/tick_usage
//keep running while we have stuff to run and we haven't gone over a tick
// this is so subsystems paused eariler can use tick time that later subsystems never used
// this is so subsystems paused eariler can use tick time that later subsystems never used
while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC)
ran = FALSE
bg_calc = FALSE
@@ -459,20 +483,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if (!(queue_node_flags & SS_TICKER) && skip_ticks)
queue_node = queue_node.queue_next
continue
//super special case, subsystems where we can't make them pause mid way through
//if we can't run them this tick (without going over a tick)
//we bump up their priority and attempt to run them next tick
//(unless we haven't even ran anything this tick, since its unlikely they will ever be able run
// in those cases, so we just let them run)
if (queue_node_flags & SS_NO_TICK_CHECK)
if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker)
if (!(queue_node_flags & SS_BACKGROUND))
queue_node.queued_priority += queue_priority_count * 0.1
queue_priority_count -= queue_node_priority
queue_priority_count += queue_node.queued_priority
current_tick_budget -= queue_node_priority
queue_node = queue_node.queue_next
continue
if (!bg_calc && (queue_node_flags & SS_BACKGROUND))
current_tick_budget = queue_priority_count_bg
@@ -481,7 +491,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
tick_remaining = TICK_LIMIT_RUNNING - TICK_USAGE
if (current_tick_budget > 0 && queue_node_priority > 0)
tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority)
//Give the subsystem a precentage of the remaining tick based on the remaning priority
tick_precentage = tick_remaining * (queue_node_priority / current_tick_budget)
else
tick_precentage = tick_remaining
@@ -489,8 +500,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
current_ticklimit = round(TICK_USAGE + tick_precentage)
if (!(queue_node_flags & SS_TICKER))
ran_non_ticker = TRUE
ran = TRUE
queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING)
@@ -535,14 +544,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
queue_node.last_fire = world.time
queue_node.times_fired++
if (queue_node_flags & SS_TICKER)
queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait)
else if (queue_node_flags & SS_POST_FIRE_TIMING)
queue_node.next_fire = world.time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100))
else if (queue_node_flags & SS_KEEP_TIMING)
queue_node.next_fire += queue_node.wait
else
queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100))
queue_node.update_nextfire()
queue_node.queued_time = 0
@@ -554,7 +556,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
. = 1
//resets the queue, and all subsystems, while filtering out the subsystem lists
// called if any mc's queue procs runtime or exit improperly.
// called if any mc's queue procs runtime or exit improperly.
/datum/controller/master/proc/SoftReset(list/ticker_SS, list/runlevel_SS)
. = 0
log_world("MC: SoftReset called, resetting MC queue state.")
+43 -14
View File
@@ -29,11 +29,11 @@
var/initialized = FALSE
/// Set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later
/// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
/// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
var/can_fire = TRUE
///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
/*
* The following variables are managed by the MC and should not be modified directly.
@@ -69,6 +69,9 @@
/// Tracks the amount of completed runs for the subsystem
var/times_fired = 0
/// How many fires have we been requested to postpone
var/postponed_fires = 0
/// Time the subsystem entered the queue, (for timing and priority reasons)
var/queued_time = 0
@@ -122,12 +125,38 @@
dequeue()
can_fire = 0
flags |= SS_NO_FIRE
Master.subsystems -= src
if (Master)
Master.subsystems -= src
return ..()
/** Update next_fire for the next run.
* reset_time (bool) - Ignore things that would normally alter the next fire, like tick_overrun, and last_fire. (also resets postpone)
*/
/datum/controller/subsystem/proc/update_nextfire(reset_time = FALSE)
var/queue_node_flags = flags
if (reset_time)
postponed_fires = 0
if (queue_node_flags & SS_TICKER)
next_fire = world.time + (world.tick_lag * wait)
else
next_fire = world.time + wait
return
if (queue_node_flags & SS_TICKER)
next_fire = world.time + (world.tick_lag * wait)
else if (queue_node_flags & SS_POST_FIRE_TIMING)
next_fire = world.time + wait + (world.tick_lag * (tick_overrun/100))
else if (queue_node_flags & SS_KEEP_TIMING)
next_fire += wait
else
next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100))
//Queue it to run.
// (we loop thru a linked list until we get to the end or find the right point)
// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
// (we loop thru a linked list until we get to the end or find the right point)
// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
/datum/controller/subsystem/proc/enqueue()
var/SS_priority = priority
var/SS_flags = flags
@@ -191,9 +220,9 @@
queue_next.queue_prev = queue_prev
if (queue_prev)
queue_prev.queue_next = queue_next
if (src == Master.queue_tail)
if (Master && (src == Master.queue_tail))
Master.queue_tail = queue_prev
if (src == Master.queue_head)
if (Master && (src == Master.queue_head))
Master.queue_head = queue_next
queued_time = 0
if (state == SS_QUEUED)
@@ -217,10 +246,11 @@
//used to initialize the subsystem AFTER the map has loaded
/datum/controller/subsystem/Initialize(start_timeofday)
initialized = TRUE
// SEND_SIGNAL(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, start_timeofday)
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!"
to_chat(world, "<span class='boldannounce'>[msg]</span>")
log_subsystem("INIT", msg)
to_chat(world, span_boldannounce("[msg]"))
log_subsystem(msg)
return time
/datum/controller/subsystem/stat_entry(msg)
@@ -243,11 +273,10 @@
if (SS_IDLE)
. = " "
//could be used to postpone a costly subsystem for (default one) var/cycles, cycles
//for instance, during cpu intensive operations like explosions
/// Causes the next "cycle" fires to be missed. Effect is accumulative but can reset by calling update_nextfire(reset_time = TRUE)
/datum/controller/subsystem/proc/postpone(cycles = 1)
if(next_fire - world.time < wait)
next_fire += (wait*cycles)
if (can_fire && cycles >= 1)
postponed_fires += cycles
//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
//should attempt to salvage what it can from the old instance of subsystem
@@ -258,7 +287,7 @@
if (NAMEOF(src, can_fire))
//this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag
if (var_value)
next_fire = world.time + wait
update_nextfire(reset_time = TRUE)
if (NAMEOF(src, queued_priority)) //editing this breaks things.
return FALSE
. = ..()
+32 -3
View File
@@ -30,6 +30,7 @@ SUBSYSTEM_DEF(air)
var/list/networks = list()
var/list/pipenets_needing_rebuilt = list()
var/list/deferred_airs = list()
var/cur_deferred_airs = 0
var/max_deferred_airs = 0
var/list/obj/machinery/atmos_machinery = list()
var/list/obj/machinery/atmos_air_machinery = list()
@@ -54,14 +55,22 @@ SUBSYSTEM_DEF(air)
var/equalize_turf_limit = 10
// Max number of turfs to look for a space turf, and max number of turfs that will be decompressed.
var/equalize_hard_turf_limit = 2000
// Whether equalization should be enabled at all.
// Whether equalization is enabled. Can be disabled for performance reasons.
var/equalize_enabled = TRUE
// Whether equalization should be enabled.
var/should_do_equalization = TRUE
// When above 0, won't equalize; performance handling
var/eq_cooldown = 0
// Whether turf-to-turf heat exchanging should be enabled.
var/heat_enabled = FALSE
// Max number of times process_turfs will share in a tick.
var/share_max_steps = 3
// Target for share_max_steps; can go below this, if it determines the thread is taking too long.
var/share_max_steps_target = 3
// Excited group processing will try to equalize groups with total pressure difference less than this amount.
var/excited_group_pressure_goal = 1
// Target for excited_group_pressure_goal; can go below this, if it determines the thread is taking too long.
var/excited_group_pressure_goal_target = 1
// If this is set to 0, monstermos won't process planet atmos
var/planet_equalize_enabled = 0
@@ -221,6 +230,7 @@ SUBSYSTEM_DEF(air)
// This also happens to do all the commented out stuff below, all in a single separate thread. This is mostly so that the
// waiting is consistent.
if(currentpart == SSAIR_ACTIVETURFS)
run_delay_heuristics()
timer = TICK_USAGE_REAL
process_turfs(resumed)
cost_turfs = MC_AVERAGE(cost_turfs, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
@@ -278,7 +288,8 @@ SUBSYSTEM_DEF(air)
pipenets_needing_rebuilt += atmos_machine
/datum/controller/subsystem/air/proc/process_deferred_airs(resumed = 0)
max_deferred_airs = max(deferred_airs.len,max_deferred_airs)
cur_deferred_airs = deferred_airs.len
max_deferred_airs = max(cur_deferred_airs,max_deferred_airs)
while(deferred_airs.len)
var/list/cur_op = deferred_airs[deferred_airs.len]
deferred_airs.len--
@@ -378,6 +389,24 @@ SUBSYSTEM_DEF(air)
return
*/
/datum/controller/subsystem/air/proc/run_delay_heuristics()
if(!equalize_enabled)
cost_equalize = 0
if(should_do_equalization)
eq_cooldown--
if(eq_cooldown <= 0)
equalize_enabled = TRUE
var/total_thread_time = cost_turfs + cost_equalize + cost_groups + cost_post_process
if(total_thread_time)
var/wait_ms = wait * 100
var/delay_threshold = 1-(total_thread_time/wait_ms + cur_deferred_airs / 50)
share_max_steps = max(1,round(share_max_steps_target * delay_threshold, 1))
eq_cooldown += (1-delay_threshold) * (cost_equalize / total_thread_time) * 2
if(eq_cooldown > 0.5)
equalize_enabled = FALSE
excited_group_pressure_goal = max(0,excited_group_pressure_goal_target * (1 - delay_threshold))
/datum/controller/subsystem/air/proc/process_turfs(resumed = 0)
if(process_turfs_auxtools(resumed,TICK_REMAINING_MS))
pause()
@@ -402,7 +431,7 @@ SUBSYSTEM_DEF(air)
pause()
/datum/controller/subsystem/air/proc/finish_turf_processing(resumed = 0)
if(finish_turf_processing_auxtools(TICK_REMAINING_MS))
if(finish_turf_processing_auxtools(TICK_REMAINING_MS) || thread_running())
pause()
/datum/controller/subsystem/air/proc/post_process_turfs(resumed = 0)
+97 -100
View File
@@ -29,37 +29,35 @@ SUBSYSTEM_DEF(garbage)
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
init_order = INIT_ORDER_GARBAGE
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_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
var/gcedlasttick = 0 // number of things that gc'ed last tick
var/delslasttick = 0 // number of del()'s we've done this tick
var/gcedlasttick = 0 // number of things that gc'ed last tick
var/totaldels = 0
var/totalgcs = 0
var/highest_del_time = 0
var/highest_del_tickusage = 0
var/highest_del_ms = 0
var/highest_del_type_string = ""
var/list/pass_counts
var/list/fail_counts
var/list/items = list() // Holds our qdel_item statistics datums
var/list/items = list() // Holds our qdel_item statistics datums
//Queue
var/list/queues
#ifdef LEGACY_REFERENCE_TRACKING
#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
#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()
@@ -90,13 +88,18 @@ SUBSYSTEM_DEF(garbage)
for(var/path in items)
var/datum/qdel_item/I = items[path]
dellog += "Path: [path]"
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
dellog += "\tSUSPENDED FOR LAG"
if (I.failures)
dellog += "\tFailures: [I.failures]"
dellog += "\tqdel() Count: [I.qdels]"
dellog += "\tDestroy() Cost: [I.destroy_time]ms"
if (I.hard_deletes)
dellog += "\tTotal Hard Deletes [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"
if (I.hard_deletes_over_threshold)
dellog += "\tHard Deletes Over Threshold: [I.hard_deletes_over_threshold]"
if (I.slept_destroy)
dellog += "\tSleeps: [I.slept_destroy]"
if (I.no_respect_force)
@@ -122,6 +125,15 @@ SUBSYSTEM_DEF(garbage)
/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_CHECK)
if (level == GC_QUEUE_CHECK)
@@ -153,7 +165,6 @@ SUBSYSTEM_DEF(garbage)
if(GCd_at_time > cut_off_time)
break // Everything else is newer, skip them
count++
var/refID = L[2]
var/datum/D
D = locate(refID)
@@ -162,8 +173,8 @@ SUBSYSTEM_DEF(garbage)
++gcedlasttick
++totalgcs
pass_counts[level]++
#ifdef LEGACY_REFERENCE_TRACKING
reference_find_on_fail -= refID //It's deleted we don't care anymore.
#ifdef REFERENCE_TRACKING
reference_find_on_fail -= refID //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
return
@@ -171,35 +182,43 @@ SUBSYSTEM_DEF(garbage)
// Something's still referring to the qdel'd object.
fail_counts[level]++
#ifdef REFERENCE_TRACKING
var/ref_searching = FALSE
#endif
switch (level)
if (GC_QUEUE_CHECK)
#ifdef REFERENCE_TRACKING
D.find_references()
#elif defined(LEGACY_REFERENCE_TRACKING)
if(reference_find_on_fail[refID])
D.find_references_legacy()
INVOKE_ASYNC(D, /datum/proc/find_references)
ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
else
D.find_references_legacy()
INVOKE_ASYNC(D, /datum/proc/find_references)
ref_searching = TRUE
#endif
reference_find_on_fail -= refID
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
#ifdef TESTING
log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
#ifdef TESTING
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 --")
testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
#endif
#ifdef REFERENCE_TRACKING
GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only.
continue
#endif
I.failures++
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
#ifdef REFERENCE_TRACKING
if(ref_searching)
return //ref searching intentionally cancels all further fires while running so things that hold references don't end up getting deleted, so we want to return here instead of continue
#endif
continue
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
if (MC_TICK_CHECK)
@@ -208,27 +227,17 @@ SUBSYSTEM_DEF(garbage)
Queue(D, level+1)
#ifdef REFERENCE_TRACKING
if(ref_searching)
return
#endif
if (MC_TICK_CHECK)
return
if (count)
queue.Cut(1,count+1)
count = 0
#ifdef LEGACY_REFERENCE_TRACKING
/datum/controller/subsystem/garbage/proc/add_type_to_findref(type)
if(!ispath(type))
return "NOT A VAILD PATH"
reference_find_on_fail_types |= typecacheof(type)
/datum/controller/subsystem/garbage/proc/remove_type_from_findref(type)
if(!ispath(type))
return "NOT A VALID PATH"
reference_find_on_fail_types -= typesof(type)
/datum/controller/subsystem/garbage/proc/clear_findref_types()
reference_find_on_fail_types = list()
#endif
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
if (isnull(D))
return
@@ -238,63 +247,66 @@ SUBSYSTEM_DEF(garbage)
var/gctime = world.time
var/refid = "\ref[D]"
#ifdef LEGACY_REFERENCE_TRACKING
if(reference_find_on_fail_types[D.type])
SSgarbage.reference_find_on_fail[REF(D)] = TRUE
#endif
D.gc_destroyed = gctime
var/list/queue = queues[level]
queue[++queue.len] = list(gctime, refid) // not += for byond reasons
//this is mainly to separate things profile wise.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
var/time = world.timeofday
var/tick = TICK_USAGE
var/ticktime = world.time
++delslasttick
++totaldels
var/type = D.type
var/refID = "\ref[D]"
var/tick_usage = TICK_USAGE
del(D)
tick = (TICK_USAGE-tick+((world.time-ticktime)/world.tick_lag*100))
tick_usage = TICK_USAGE_TO_MS(tick_usage)
var/datum/qdel_item/I = items[type]
I.hard_deletes++
I.hard_delete_time += TICK_DELTA_TO_MS(tick)
I.hard_delete_time += tick_usage
if (tick_usage > I.hard_delete_max)
I.hard_delete_max = tick_usage
if (tick_usage > highest_del_ms)
highest_del_ms = tick_usage
highest_del_type_string = "[type]"
var/time = MS2DS(tick_usage)
if (tick > highest_del_tickusage)
highest_del_tickusage = tick
time = world.timeofday - time
if (!time && TICK_DELTA_TO_MS(tick) > 1)
time = TICK_DELTA_TO_MS(tick)/100
if (time > highest_del_time)
highest_del_time = time
if (time > 10)
log_game("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete)")
message_admins("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete).")
if (time > 0.1 SECONDS)
postpone(time)
var/threshold = CONFIG_GET(number/hard_deletes_overrun_threshold)
if (threshold && (time > threshold SECONDS))
if (!(I.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).")
I.qdel_flags |= QDEL_ITEM_ADMINS_WARNED
I.hard_deletes_over_threshold++
var/overrun_limit = CONFIG_GET(number/hard_deletes_overrun_limit)
if (overrun_limit && I.hard_deletes_over_threshold >= overrun_limit)
I.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]
/// Qdel Item: Holds statistics on each type that passes thru qdel
/datum/qdel_item
var/name = ""
var/qdels = 0 //Total number of times it's passed thru qdel.
var/destroy_time = 0 //Total amount of milliseconds spent processing this type's Destroy()
var/failures = 0 //Times it was queued for soft deletion but failed to soft delete.
var/hard_deletes = 0 //Different from failures because it also includes QDEL_HINT_HARDDEL deletions
var/hard_delete_time = 0//Total amount of milliseconds spent hard deleting this type.
var/no_respect_force = 0//Number of times it's not respected force=TRUE
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/name = "" //!Holds the type as a string for this type
var/qdels = 0 //!Total number of times it's passed thru qdel.
var/destroy_time = 0 //!Total amount of milliseconds spent processing this type's Destroy()
var/failures = 0 //!Times it was queued for soft deletion but failed to soft delete.
var/hard_deletes = 0 //!Different from failures because it also includes QDEL_HINT_HARDDEL deletions
var/hard_delete_time = 0 //!Total amount of milliseconds spent hard deleting this type.
var/hard_delete_max = 0 //!Highest time spent hard_deleting this in ms.
var/hard_deletes_over_threshold = 0 //!Number of times hard deletes took longer than the configured threshold
var/no_respect_force = 0 //!Number of times it's not respected force=TRUE
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.
/datum/qdel_item/New(mytype)
name = "[mytype]"
@@ -307,12 +319,12 @@ SUBSYSTEM_DEF(garbage)
if(!istype(D))
del(D)
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++
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
@@ -328,12 +340,12 @@ SUBSYSTEM_DEF(garbage)
if(!D)
return
switch(hint)
if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
SSgarbage.Queue(D)
if (QDEL_HINT_IWILLGC)
D.gc_destroyed = world.time
return
if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
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
@@ -350,17 +362,17 @@ SUBSYSTEM_DEF(garbage)
I.no_respect_force++
SSgarbage.Queue(D)
if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
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.
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
#ifdef LEGACY_REFERENCE_TRACKING
if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
#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_legacy()
if (QDEL_HINT_IFFAIL_FINDREFERENCE)
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.reference_find_on_fail["\ref[D]"] = TRUE
#endif
else
#ifdef TESTING
@@ -371,18 +383,3 @@ SUBSYSTEM_DEF(garbage)
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")
#ifdef TESTING
/proc/writeDatumCount()
var/list/datums = list()
for(var/datum/D in world)
datums[D.type] += 1
for(var/datum/D)
datums[D.type] += 1
datums = sortTim(datums, /proc/cmp_numeric_dsc, associative = TRUE)
if(fexists("data/DATUMCOUNT.txt"))
fdel("data/DATUMCOUNT.txt")
var/outfile = file("data/DATUMCOUNT.txt")
for(var/path in datums)
outfile << "[datums[path]]\t\t\t\t\t[path]"
#endif
+8 -2
View File
@@ -366,10 +366,16 @@ SUBSYSTEM_DEF(research)
techweb_categories[I.category] = list(I.id = TRUE)
/datum/controller/subsystem/research/proc/techweb_node_by_id(id)
return techweb_nodes[id] || error_node
if(techweb_nodes[id])
return techweb_nodes[id]
stack_trace("Attempted to access node ID [id] which didn't exist")
return error_node
/datum/controller/subsystem/research/proc/techweb_design_by_id(id)
return techweb_designs[id] || error_design
if(techweb_designs[id])
return techweb_designs[id]
stack_trace("Attempted to access design ID [id] which didn't exist")
return error_design
/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D)
for(var/i in techweb_nodes)
+2 -6
View File
@@ -12,9 +12,7 @@
GAS_CO2=10,
)
restricted_gases = list(
GAS_PLASMA=0.1,
GAS_BZ=1.2,
GAS_METHANE=1.0,
GAS_BZ=0.1,
GAS_METHYL_BROMIDE=0.1,
)
restricted_chance = 30
@@ -23,7 +21,7 @@
maximum_pressure = LAVALAND_EQUIPMENT_EFFECT_PRESSURE - 1
minimum_temp = BODYTEMP_COLD_DAMAGE_LIMIT + 1
maximum_temp = 350
maximum_temp = 320
/datum/atmosphere/icemoon
id = ICEMOON_DEFAULT_ATMOS
@@ -38,8 +36,6 @@
GAS_CO2=10,
)
restricted_gases = list(
GAS_PLASMA=0.1,
GAS_METHANE=1.0,
GAS_METHYL_BROMIDE=0.1,
)
restricted_chance = 10
+1
View File
@@ -71,6 +71,7 @@
R.update_icon()
to_chat(user, "<span class='info'>You strengthen [R], improving its resistance against melee, bullet and laser damage.</span>")
else
SEND_SIGNAL(O, COMSIG_ARMOR_PLATED, amount, maxamount)
to_chat(user, "<span class='info'>You strengthen [O], improving its resistance against melee attacks.</span>")
+5 -1
View File
@@ -57,9 +57,13 @@
*/
var/list/cooldowns
#ifdef TESTING
#ifdef REFERENCE_TRACKING
var/running_find_references
var/last_find_references = 0
#ifdef REFERENCE_TRACKING_DEBUG
///Stores info about where refs are found, used for sanity checks and testing
var/list/found_refs
#endif
#endif
#ifdef DATUMVAR_DEBUGGING_MODE
@@ -0,0 +1,32 @@
/**
Stack End Detector.
Can detect if a given code stack has exited, used by the mc for stack overflow detection.
**/
/datum/stack_end_detector
var/datum/weakref/_WF
var/datum/stack_canary/_canary
/datum/stack_end_detector/New()
_canary = new()
_WF = WEAKREF(_canary)
/** Prime the stack overflow detector.
Store the return value of this proc call in a proc level var.
Can only be called once.
**/
/datum/stack_end_detector/proc/prime_canary()
if (!_canary)
CRASH("Prime_canary called twice")
. = _canary
_canary = null
/// Returns true if the stack is still going. Calling before the canary has been primed also returns true
/datum/stack_end_detector/proc/check()
return !!_WF.resolve()
/// Stack canary. Will go away if the stack it was primed by is ended by byond for return or stack overflow reasons.
/datum/stack_canary
/// empty proc to avoid warnings about unused variables. Call this proc on your canary in the stack it's watching.
/datum/stack_canary/proc/use_variable()
+11 -3
View File
@@ -6,10 +6,12 @@
var/body
var/headers
var/url
/// If present response body will be saved to this file.
var/output_file
var/_raw_response
/datum/http_request/proc/prepare(method, url, body = "", list/headers)
/datum/http_request/proc/prepare(method, url, body = "", list/headers, output_file)
if (!length(headers))
headers = ""
else
@@ -19,15 +21,16 @@
src.url = url
src.body = body
src.headers = headers
src.output_file = output_file
/datum/http_request/proc/execute_blocking()
_raw_response = rustg_http_request_blocking(method, url, body, headers)
_raw_response = rustg_http_request_blocking(method, url, body, headers, build_options())
/datum/http_request/proc/begin_async()
if (in_progress)
CRASH("Attempted to re-use a request object.")
id = rustg_http_request_async(method, url, body, headers)
id = rustg_http_request_async(method, url, body, headers, build_options())
if (isnull(text2num(id)))
stack_trace("Proc error: [id]")
@@ -35,6 +38,11 @@
else
in_progress = TRUE
/datum/http_request/proc/build_options()
if(output_file)
return json_encode(list("output_filename"=output_file,"body_filename"=null))
return null
/datum/http_request/proc/is_complete()
if (isnull(id))
return TRUE
+17
View File
@@ -162,3 +162,20 @@
gain_text = "<span class='notice'>You feel like munching on a can of soda.</span>"
lose_text = "<span class='notice'>You no longer feel like you should be eating trash.</span>"
mob_trait = TRAIT_TRASHCAN
/datum/quirk/colorist
name = "Colorist"
desc = "You like carrying around a hair dye spray to quickly apply color patterns to your hair."
value = 0
medical_record_text = "Patient enjoys dyeing their hair with pretty colors."
/datum/quirk/colorist/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
var/obj/item/dyespray/spraycan = new(get_turf(quirk_holder))
H.equip_to_slot(spraycan, SLOT_IN_BACKPACK)
H.regenerate_icons()
/datum/quirk/colorist/post_add()
var/mob/living/carbon/human/H = quirk_holder
SEND_SIGNAL(H.back, COMSIG_TRY_STORAGE_SHOW, H)
to_chat(quirk_holder, "<span class='boldnotice'>You brought some extra dye with you! It's in your bag if you forgot.</span>")
+3 -3
View File
@@ -151,14 +151,14 @@ Credit where due:
var/datum/team/clockcult/main_clockcult
/datum/game_mode/clockwork_cult/pre_setup() //Gamemode and job code is pain. Have fun codediving all of that stuff, whoever works on this next - Delta
/*var/list/errorList = list()
var/list/errorList = list()
var/list/reebes = SSmapping.LoadGroup(errorList, "Reebe", "map_files/generic", "City_of_Cogs.dmm", default_traits = ZTRAITS_REEBE, silent = TRUE)
if(errorList.len) // reebe failed to load
message_admins("Reebe failed to load!")
log_game("Reebe failed to load!")
return FALSE
for(var/datum/parsed_map/PM in reebes) //Temporarily commented because of z-level loading reliably segfaulting the server.
PM.initTemplateBounds()*/
PM.initTemplateBounds()
if(CONFIG_GET(flag/protect_roles_from_antagonist))
restricted_jobs += protected_jobs
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
@@ -275,7 +275,7 @@ Credit where due:
ears = /obj/item/radio/headset
gloves = /obj/item/clothing/gloves/color/yellow
belt = /obj/item/storage/belt/utility/servant
backpack_contents = list(/obj/item/storage/box/engineer = 1, \
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/clockwork/replica_fabricator = 1, /obj/item/stack/tile/brass/fifty = 1, /obj/item/reagent_containers/food/drinks/bottle/holyoil = 1)
id = /obj/item/pda
var/plasmaman //We use this to determine if we should activate internals in post_equip()
+1 -1
View File
@@ -43,7 +43,7 @@
l_pocket = /obj/item/pinpointer/nuke/syndicate
r_pocket = /obj/item/bikehorn
id = /obj/item/card/id/syndicate
backpack_contents = list(/obj/item/storage/box/syndie=1,\
backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\
/obj/item/kitchen/knife/combat/survival,
/obj/item/reagent_containers/spray/waterflower/lube)
implants = list(/obj/item/implant/sad_trombone)
+3 -3
View File
@@ -127,7 +127,7 @@
l_pocket = /obj/item/pinpointer/nuke/syndicate
id = /obj/item/card/id/syndicate
belt = /obj/item/gun/ballistic/automatic/pistol
backpack_contents = list(/obj/item/storage/box/syndie=1,\
backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\
/obj/item/kitchen/knife/combat/survival)
var/tc = 25
@@ -173,7 +173,7 @@
internals_slot = SLOT_R_STORE
belt = /obj/item/storage/belt/military
r_hand = /obj/item/gun/ballistic/automatic/shotgun/bulldog
backpack_contents = list(/obj/item/storage/box/syndie=1,\
backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\
/obj/item/tank/jetpack/oxygen/harness=1,\
/obj/item/gun/ballistic/automatic/pistol=1,\
/obj/item/kitchen/knife/combat/survival)
@@ -188,7 +188,7 @@
r_pocket = /obj/item/tank/internals/emergency_oxygen/engi
internals_slot = SLOT_R_STORE
belt = /obj/item/storage/belt/military
backpack_contents = list(/obj/item/storage/box/syndie=1,\
backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\
/obj/item/tank/jetpack/oxygen/harness=1,\
/obj/item/gun/ballistic/automatic/pistol=1,\
/obj/item/kitchen/knife/combat/survival)
+8 -2
View File
@@ -568,6 +568,12 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
if(usr.incapacitated() || !Adjacent(usr) || usr.lying)
return
if(iscyborg(usr))
var/obj/item/gripper/gripper = usr.get_active_held_item(TRUE)
if(istype(gripper))
gripper.pre_attack(src, usr, get_dist(src, usr))
return
if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota
usr.UnarmedAttack(src)
@@ -879,7 +885,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
/obj/item/MouseEntered(location, control, params)
SEND_SIGNAL(src, COMSIG_ITEM_MOUSE_ENTER, location, control, params)
if((item_flags & IN_INVENTORY || item_flags & IN_STORAGE) && usr.client.prefs.enable_tips && !QDELETED(src) || isobserver(usr))
if((item_flags & IN_INVENTORY || item_flags & IN_STORAGE) && usr.client.prefs.enable_tips && !QDELETED(src))
var/timedelay = usr.client.prefs.tip_delay/100
var/user = usr
tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE)//timer takes delay in deciseconds, but the pref is in milliseconds. dividing by 100 converts it.
@@ -900,7 +906,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
remove_outline()
/obj/item/proc/apply_outline(colour = null)
if(!(item_flags & IN_INVENTORY || item_flags & IN_STORAGE) || QDELETED(src))
if(!(item_flags & IN_INVENTORY || item_flags & IN_STORAGE) || QDELETED(src) || isobserver(usr))
return
if(usr.client)
if(!usr.client.prefs.outline_enabled)
+40
View File
@@ -0,0 +1,40 @@
/obj/item/dyespray
name = "hair dye spray"
desc = "A spray to dye your hair any gradients you'd like."
icon = 'icons/obj/dyespray.dmi'
icon_state = "dyespray"
/obj/item/dyespray/attack_self(mob/user)
dye(user)
/obj/item/dyespray/pre_attack(atom/target, mob/living/user, params)
dye(target)
return ..()
/**
* Applies a gradient and a gradient color to a mob.
*
* Arguments:
* * target - The mob who we will apply the gradient and gradient color to.
*/
/obj/item/dyespray/proc/dye(mob/target)
if(!ishuman(target))
return
var/mob/living/carbon/human/human_target = target
var/new_grad_style = input(usr, "Choose a color pattern:", "Character Preference") as null|anything in GLOB.hair_gradients_list
if(!new_grad_style)
return
var/new_grad_color = input(usr, "Choose a secondary hair color:", "Character Preference","#"+human_target.grad_color) as color|null
if(!new_grad_color)
return
human_target.grad_style = new_grad_style
human_target.grad_color = sanitize_hexcolor(new_grad_color)
to_chat(human_target, "<span class='notice'>You start applying the hair dye...</span>")
if(!do_after(usr, 30, target = human_target))
return
playsound(src, 'sound/effects/spray.ogg', 5, TRUE, 5)
human_target.update_hair()
+1 -1
View File
@@ -184,7 +184,7 @@
hitcost = 75 //Costs more than a standard cyborg esword
w_class = WEIGHT_CLASS_NORMAL
sharpness = SHARP_EDGED
light_color = "#40ceff"
light_color = LIGHT_COLOR_RED
tool_behaviour = TOOL_SAW
toolspeed = 0.7
+109 -25
View File
@@ -745,9 +745,9 @@
Grippers oh god oh fuck
***********************************************************************/
/obj/item/weapon/gripper
/obj/item/gripper
name = "engineering gripper"
desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more. Alt click to drop instead of use."
desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more."
icon = 'icons/obj/device.dmi'
icon_state = "gripper"
@@ -776,18 +776,29 @@
var/obj/item/wrapped = null // Item currently being held.
//Used to interact with UI's of held items, such as gas tanks and airlock electronics.
/obj/item/weapon/gripper/AltClick(mob/user)
// Used to drop whatever's in the gripper.
/obj/item/gripper/proc/drop_held(silent = FALSE)
if(wrapped)
wrapped.forceMove(get_turf(wrapped))
to_chat(user, "<span class='notice'>You drop the [wrapped].</span>")
if(!silent)
to_chat(usr, "<span class='notice'>You drop the [wrapped].</span>")
modify_appearance(wrapped, FALSE)
wrapped = null
return ..()
update_appearance()
return TRUE
return FALSE
/obj/item/weapon/gripper/pre_attack(var/atom/target, var/mob/living/silicon/robot/user, proximity, params)
/obj/item/gripper/proc/takeitem(obj/item/item, silent = FALSE)
if(!silent)
to_chat(usr, "<span class='notice'>You collect \the [item].</span>")
item.loc = src
wrapped = item
update_appearance()
if(!proximity)
return
/obj/item/gripper/pre_attack(atom/target, mob/living/silicon/robot/user, params)
var/proximity = get_dist(user, target)
if(proximity > 1)
return STOP_ATTACK_PROC_CHAIN
if(!wrapped)
for(var/obj/item/thing in src.contents)
@@ -795,21 +806,24 @@
break
if(wrapped) //Already have an item.
var/obj/item/item = wrapped
drop_held(TRUE)
//Temporary put wrapped into user so target's attackby() checks pass.
wrapped.loc = user
item.loc = user
//Pass the attack on to the target. This might delete/relocate wrapped.
var/resolved = target.attackby(wrapped,user)
if(!resolved && wrapped && target)
wrapped.afterattack(target,user,1)
var/resolved = target.attackby(item, user, params)
if(!resolved && item && target)
item.afterattack(target, user, proximity, params)
//If wrapped was neither deleted nor put into target, put it back into the gripper.
if(wrapped && user && (wrapped.loc == user))
wrapped.loc = src
else
wrapped = null
if(item && user && (item.loc == user))
takeitem(item, TRUE)
return
else
item = null
return STOP_ATTACK_PROC_CHAIN
else if(istype(target,/obj/item))
else if(isitem(target))
var/obj/item/I = target
var/grab = 0
@@ -824,24 +838,94 @@
//We can grab the item, finally.
if(grab)
to_chat(user, "<span class='notice'>You collect \the [I].</span>")
I.loc = src
wrapped = I
takeitem(I)
return
else
to_chat(user, "<span class='danger'>Your gripper cannot hold \the [target].</span>")
/obj/item/weapon/gripper/mining
// Rare cases - meant to be handled by code\modules\mob\living\silicon\robot\robot.dm:584 and the weirdness of get_active_held_item() of borgs.
/obj/item/gripper/attack_self(mob/user)
if(wrapped)
wrapped.attack_self(user)
return
. = ..()
// Splitable items
/obj/item/gripper/AltClick(mob/user)
if(wrapped)
wrapped.AltClick(user)
return
. = ..()
// Even rarer cases
/obj/item/gripper/CtrlClick(mob/user)
if(wrapped)
wrapped.CtrlClick(user)
return
. = ..()
// At this point you're just kidding me, but have this one as well.
/obj/item/gripper/CtrlShiftClick(mob/user)
if(wrapped)
wrapped.CtrlShiftClick(user)
return
. = ..()
// Make it clear what we can do with it.
/obj/item/gripper/examine(mob/user)
. = ..()
if(wrapped)
. += "<span class='notice'>It is holding [icon2html(wrapped, user)] [wrapped].</span>"
. += "<span class='notice'>Examine the little preview to examine it.</span>"
. += "<span class='notice'>Attempting to drop the gripper will only drop [wrapped].</span>"
// Resets vis_contents and if holding something, add it to vis_contents.
/obj/item/gripper/update_appearance(updates)
. = ..()
vis_contents = list()
if(wrapped)
modify_appearance(wrapped, TRUE)
vis_contents += wrapped
// Generates the "minified" version of the item being held and adjust it's position.
/obj/item/gripper/proc/modify_appearance(obj/item, minify = FALSE)
if(minify)
var/matrix/new_transform = new
new_transform.Scale(0.5, 0.5)
item.transform = new_transform
item.pixel_x = 8
item.pixel_y = -8
else
item.pixel_x = initial(pixel_x)
item.pixel_y = initial(pixel_y)
item.transform = new
// I kind of wanted the item to be held in the gripper when stored as well, but i realized "store" is just drop as well, so i'll do this for now.
// This will handle cases where the borg runs out of power or is damaged enough so the module is forcefully stored.
/obj/item/gripper/cyborg_unequip(mob/user)
. = ..()
if(wrapped)
drop_held()
// Clear references on being destroyed
/obj/item/Destroy()
for(var/obj/item/gripper/gripper in vis_locs)
if(gripper.wrapped == src)
gripper.wrapped = null
gripper.update_appearance()
. = ..()
/obj/item/gripper/mining
name = "shelter capsule deployer"
desc = "A simple grasping tool for carrying and deploying shelter capsules. Alt click to drop instead of use."
desc = "A simple grasping tool for carrying and deploying shelter capsules."
icon_state = "gripper_mining"
can_hold = list(
/obj/item/survivalcapsule
)
/obj/item/weapon/gripper/medical
/obj/item/gripper/medical
name = "medical gripper"
desc = "A simple grasping tool for interacting with medical equipment, such as beakers, blood bags, chem bags and more. Alt click to drop instead of use."
desc = "A simple grasping tool for interacting with medical equipment, such as beakers, blood bags, chem bags and more."
icon_state = "gripper_medical"
can_hold = list(
/obj/item/storage/bag/bio,
@@ -502,6 +502,8 @@ GLOBAL_LIST_INIT(cardboard_recipes, list ( \
new /datum/stack_recipe("sterile masks box", /obj/item/storage/box/masks), \
new /datum/stack_recipe("body bag box", /obj/item/storage/box/bodybags), \
new /datum/stack_recipe("prescription glasses box", /obj/item/storage/box/rxglasses), \
new /datum/stack_recipe("oxygen tank box", /obj/item/storage/box/emergencytank), \
new /datum/stack_recipe("extended oxygen tank box", /obj/item/storage/box/engitank), \
null, \
new /datum/stack_recipe("disk box", /obj/item/storage/box/disks), \
new /datum/stack_recipe("light tubes box", /obj/item/storage/box/lights/tubes), \
+55 -31
View File
@@ -102,12 +102,22 @@
new /obj/item/disk/nanite_program(src)
// Ordinary survival box
/obj/item/storage/box/survival
name = "survival box"
desc = "A box with the bare essentials of ensuring the survival of you and others."
icon_state = "internals"
illustration = "emergencytank"
var/mask_type = /obj/item/clothing/mask/breath
var/internal_type = /obj/item/tank/internals/emergency_oxygen
var/medipen_type = /obj/item/reagent_containers/hypospray/medipen
/obj/item/storage/box/survival/PopulateContents()
new /obj/item/clothing/mask/breath(src)
new /obj/item/reagent_containers/hypospray/medipen(src)
new mask_type(src)
if(!isnull(medipen_type))
new medipen_type(src)
if(!isplasmaman(loc))
new /obj/item/tank/internals/emergency_oxygen(src)
new internal_type(src)
else
new /obj/item/tank/internals/plasmaman/belt(src)
@@ -115,10 +125,13 @@
..() // we want the survival stuff too.
new /obj/item/radio/off(src)
/obj/item/storage/box/survival_mining/PopulateContents()
new /obj/item/clothing/mask/gas/explorer(src)
// Mining survival box
/obj/item/storage/box/survival/mining
mask_type = /obj/item/clothing/mask/gas/explorer
/obj/item/storage/box/survival/mining/PopulateContents()
..()
new /obj/item/crowbar/red(src)
new /obj/item/reagent_containers/hypospray/medipen(src)
if(!isplasmaman(loc))
new /obj/item/tank/internals/emergency_oxygen(src)
@@ -126,39 +139,30 @@
new /obj/item/tank/internals/plasmaman/belt(src)
// Engineer survival box
/obj/item/storage/box/engineer/PopulateContents()
new /obj/item/clothing/mask/breath(src)
new /obj/item/reagent_containers/hypospray/medipen(src)
/obj/item/storage/box/survival/engineer
name = "extended-capacity survival box"
desc = "A box with the bare essentials of ensuring the survival of you and others. This one is labelled to contain an extended-capacity tank."
illustration = "extendedtank"
internal_type = /obj/item/tank/internals/emergency_oxygen/engi
if(!isplasmaman(loc))
new /obj/item/tank/internals/emergency_oxygen/engi(src)
else
new /obj/item/tank/internals/plasmaman/belt(src)
/obj/item/storage/box/engineer/radio/PopulateContents()
/obj/item/storage/box/survival/engineer/radio/PopulateContents()
..() // we want the regular items too.
new /obj/item/radio/off(src)
// Syndie survival box
/obj/item/storage/box/syndie/PopulateContents()
new /obj/item/clothing/mask/gas/syndicate(src)
if(!isplasmaman(loc))
new /obj/item/tank/internals/emergency_oxygen/engi(src)
else
new /obj/item/tank/internals/plasmaman/belt(src)
/obj/item/storage/box/survival/syndie //why is this its own thing if it's just the engi box with a syndie mask and medipen?
name = "extended-capacity survival box"
desc = "A box with the bare essentials of ensuring the survival of you and others. This one is labelled to contain an extended-capacity tank."
illustration = "extendedtank"
mask_type = /obj/item/clothing/mask/gas/syndicate
internal_type = /obj/item/tank/internals/emergency_oxygen/engi
medipen_type = null
// Security survival box
/obj/item/storage/box/security/PopulateContents()
new /obj/item/clothing/mask/gas/sechailer(src)
new /obj/item/reagent_containers/hypospray/medipen(src)
/obj/item/storage/box/survival/security
mask_type = /obj/item/clothing/mask/gas/sechailer
if(!isplasmaman(loc))
new /obj/item/tank/internals/emergency_oxygen(src)
else
new /obj/item/tank/internals/plasmaman/belt(src)
/obj/item/storage/box/security/radio/PopulateContents()
/obj/item/storage/box/survival/security/radio/PopulateContents()
..() // we want the regular stuff too
new /obj/item/radio/off(src)
@@ -1244,6 +1248,26 @@
icon_state = "box_pink"
illustration = null
/obj/item/storage/box/emergencytank
name = "emergency oxygen tank box"
desc = "A box of emergency oxygen tanks."
illustration = "emergencytank"
/obj/item/storage/box/emergencytank/PopulateContents()
..()
for(var/i in 1 to 7)
new /obj/item/tank/internals/emergency_oxygen(src) //in case anyone ever wants to do anything with spawning them, apart from crafting the box
/obj/item/storage/box/engitank
name = "extended-capacity emergency oxygen tank box"
desc = "A box of extended-capacity emergency oxygen tanks."
illustration = "extendedtank"
/obj/item/storage/box/engitank/PopulateContents()
..()
for(var/i in 1 to 7)
new /obj/item/tank/internals/emergency_oxygen/engi(src) //in case anyone ever wants to do anything with spawning them, apart from crafting the box
/obj/item/storage/box/mre //base MRE type.
name = "Nanotrasen MRE Ration Kit Menu 0"
desc = "A package containing food suspended in an outdated bluespace pocket which lasts for centuries. If you're lucky you may even be able to enjoy the meal without getting food poisoning."
@@ -7,7 +7,7 @@
new /obj/item/clothing/neck/petcollar(src) //I considered removing the pet stuff too but eh, who knows. We might get Renault back. Plus I guess you could use that collar for... other means. Aren't you supposed to be guarding the disk?
new /obj/item/pet_carrier(src)
new /obj/item/clothing/suit/armor/vest/capcarapace(src)
new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src)
new /obj/item/clothing/suit/toggle/captains_parade(src)
new /obj/item/clothing/head/crown/fancy(src)
new /obj/item/cartridge/captain(src)
new /obj/item/storage/box/silver_ids(src)
@@ -56,6 +56,7 @@
/obj/structure/closet/secure_closet/hos/PopulateContents()
..()
new /obj/item/clothing/neck/cloak/hos(src)
new /obj/item/clothing/suit/toggle/armor/hos/hos_formal(src)
new /obj/item/cartridge/hos(src)
new /obj/item/radio/headset/heads/hos(src)
new /obj/item/clothing/under/rank/security/head_of_security/parade/female(src)
+1 -1
View File
@@ -16,7 +16,7 @@ GLOBAL_LIST(topic_status_cache)
call(debug_server, "auxtools_init")()
enable_debugging()
AUXTOOLS_CHECK(AUXMOS)
#ifdef REFERENCE_TRACKING
#ifdef EXTOOLS_REFERENCE_TRACKING
enable_reference_tracking()
#endif
world.Profile(PROFILE_START)
+1 -1
View File
@@ -195,7 +195,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
// #endif
/datum/admins/proc/create_or_modify_area,
/datum/admins/proc/fixcorruption,
#ifdef REFERENCE_TRACKING
#ifdef EXTOOLS_REFERENCE_TRACKING
/datum/admins/proc/view_refs,
/datum/admins/proc/view_del_failures,
#endif
+13 -2
View File
@@ -2862,7 +2862,7 @@
return
if(!CONFIG_GET(string/centcom_ban_db))
to_chat(usr, "<span class='warning'>Centcom Galactic Ban DB is disabled!</span>")
to_chat(usr, span_warning("Centcom Galactic Ban DB is disabled!"))
return
var/ckey = href_list["centcomlookup"]
@@ -2889,8 +2889,19 @@
dat += "<center><b>0 bans detected for [ckey]</b></center>"
else
bans = json_decode(response["body"])
dat += "<center><b>[bans.len] ban\s detected for [ckey]</b></center>"
//Ignore bans from non-whitelisted sources, if a whitelist exists
var/list/valid_sources
if(CONFIG_GET(string/centcom_source_whitelist))
valid_sources = splittext(CONFIG_GET(string/centcom_source_whitelist), ",")
dat += "<center><b>Bans detected for [ckey]</b></center>"
else
//Ban count is potentially inaccurate if they're using a whitelist
dat += "<center><b>[bans.len] ban\s detected for [ckey]</b></center>"
for(var/list/ban in bans)
if(valid_sources && !(ban["sourceName"] in valid_sources))
continue
dat += "<b>Server: </b> [sanitize(ban["sourceName"])]<br>"
dat += "<b>RP Level: </b> [sanitize(ban["sourceRoleplayLevel"])]<br>"
dat += "<b>Type: </b> [sanitize(ban["type"])]<br>"
@@ -1,4 +1,4 @@
#ifdef REFERENCE_TRACKING
#ifdef EXTOOLS_REFERENCE_TRACKING
GLOBAL_LIST_EMPTY(deletion_failures)
@@ -102,29 +102,21 @@ GLOBAL_LIST_EMPTY(deletion_failures)
#endif
#ifdef LEGACY_REFERENCE_TRACKING
#ifdef REFERENCE_TRACKING
/datum/verb/legacy_find_refs()
set category = "Debug"
set name = "Find References"
set src in world
find_references_legacy(FALSE)
/datum/proc/find_references_legacy(skip_alert)
/datum/proc/find_references(skip_alert)
running_find_references = type
if(usr?.client)
if(usr.client.running_find_references)
testing("CANCELLED search for references to a [usr.client.running_find_references].")
log_reftracker("CANCELLED search for references to a [usr.client.running_find_references].")
usr.client.running_find_references = null
running_find_references = null
//restart the garbage collector
SSgarbage.can_fire = TRUE
SSgarbage.next_fire = world.time + world.tick_lag
SSgarbage.update_nextfire(reset_time = TRUE)
return
if(!skip_alert && alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes")
if(!skip_alert && tgui_alert(usr,"Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", list("Yes", "No")) != "Yes")
running_find_references = null
return
@@ -134,92 +126,122 @@ GLOBAL_LIST_EMPTY(deletion_failures)
if(usr?.client)
usr.client.running_find_references = type
testing("Beginning search for references to a [type].")
last_find_references = world.time
log_reftracker("Beginning search for references to a [type].")
var/starting_time = world.time
//Time to search the whole game for our ref
DoSearchVar(GLOB, "GLOB") //globals
log_reftracker("Finished searching globals")
DoSearchVar(GLOB) //globals
for(var/datum/thing in world) //atoms (don't beleive its lies)
DoSearchVar(thing, "World -> [thing]")
DoSearchVar(thing, "World -> [thing.type]", search_time = starting_time)
log_reftracker("Finished searching atoms")
for(var/datum/thing) //datums
DoSearchVar(thing, "World -> [thing]")
DoSearchVar(thing, "Datums -> [thing.type]", search_time = starting_time)
log_reftracker("Finished searching datums")
//Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself
for(var/client/thing) //clients
DoSearchVar(thing, "World -> [thing]")
DoSearchVar(thing, "Clients -> [thing.type]", search_time = starting_time)
log_reftracker("Finished searching clients")
log_reftracker("Completed search for references to a [type].")
testing("Completed search for references to a [type].")
if(usr?.client)
usr.client.running_find_references = null
running_find_references = null
//restart the garbage collector
SSgarbage.can_fire = TRUE
SSgarbage.next_fire = world.time + world.tick_lag
SSgarbage.update_nextfire(reset_time = TRUE)
/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64, search_time = world.time)
#ifdef REFERENCE_TRACKING_DEBUG
if(!found_refs && SSgarbage.should_save_refs)
found_refs = list()
#endif
/datum/verb/qdel_then_find_references()
set category = "Debug"
set name = "qdel() then Find References"
set src in world
qdel(src, TRUE) //force a qdel
if(!running_find_references)
find_references_legacy(TRUE)
/datum/verb/qdel_then_if_fail_find_references()
set category = "Debug"
set name = "qdel() then Find References if GC failure"
set src in world
qdel_and_find_ref_if_fail(src, TRUE)
/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64)
if(usr?.client && !usr.client.running_find_references)
return
if(!recursive_limit)
log_reftracker("Recursion limit reached. [container_name]")
return
if(istype(potential_container, /datum))
var/datum/datum_container = potential_container
if(datum_container.last_find_references == last_find_references)
return
datum_container.last_find_references = last_find_references
var/list/vars_list = datum_container.vars
for(var/varname in vars_list)
if (varname == "vars")
continue
var/variable = vars_list[varname]
if(variable == src)
testing("Found [type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]")
else if(islist(variable))
DoSearchVar(variable, "[container_name] -> list", recursive_limit - 1)
else if(islist(potential_container))
var/normal = IS_NORMAL_LIST(potential_container)
for(var/element_in_list in potential_container)
if(element_in_list == src)
testing("Found [type] \ref[src] in list [container_name].")
else if(element_in_list && !isnum(element_in_list) && normal && potential_container[element_in_list] == src)
testing("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
else if(islist(element_in_list))
DoSearchVar(element_in_list, "[container_name] -> list", recursive_limit - 1)
//Check each time you go down a layer. This makes it a bit slow, but it won't effect the rest of the game at all
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
#endif
if(istype(potential_container, /datum))
var/datum/datum_container = potential_container
if(datum_container.last_find_references == search_time)
return
datum_container.last_find_references = search_time
var/list/vars_list = datum_container.vars
for(var/varname in vars_list)
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
#endif
if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references
continue
var/variable = vars_list[varname]
if(variable == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
found_refs[varname] = TRUE
#endif
log_reftracker("Found [type] \ref[src] in [datum_container.type]'s \ref[datum_container] [varname] var. [container_name]")
continue
if(islist(variable))
DoSearchVar(variable, "[container_name] \ref[datum_container] -> [varname] (list)", recursive_limit - 1, search_time)
else if(islist(potential_container))
var/normal = IS_NORMAL_LIST(potential_container)
var/list/potential_cache = potential_container
for(var/element_in_list in potential_cache)
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
#endif
//Check normal entrys
if(element_in_list == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
found_refs[potential_cache] = TRUE
#endif
log_reftracker("Found [type] \ref[src] in list [container_name].")
continue
var/assoc_val = null
if(!isnum(element_in_list) && normal)
assoc_val = potential_cache[element_in_list]
//Check assoc entrys
if(assoc_val == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
found_refs[potential_cache] = TRUE
#endif
log_reftracker("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
continue
//We need to run both of these checks, since our object could be hiding in either of them
//Check normal sublists
if(islist(element_in_list))
DoSearchVar(element_in_list, "[container_name] -> [element_in_list] (list)", recursive_limit - 1, search_time)
//Check assoc sublists
if(islist(assoc_val))
DoSearchVar(potential_container[element_in_list], "[container_name]\[[element_in_list]\] -> [assoc_val] (list)", recursive_limit - 1, search_time)
/proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE)
SSgarbage.reference_find_on_fail[REF(thing_to_del)] = TRUE
qdel(thing_to_del, force)
thing_to_del.qdel_and_find_ref_if_fail(force)
/datum/proc/qdel_and_find_ref_if_fail(force = FALSE)
SSgarbage.reference_find_on_fail["\ref[src]"] = TRUE
qdel(src, force)
#endif
@@ -47,7 +47,7 @@
usr.client.debug_variables(src)
return
#ifdef REFERENCE_TRACKING
#ifdef EXTOOLS_REFERENCE_TRACKING
if(href_list[VV_HK_VIEW_REFERENCES])
var/datum/D = locate(href_list[VV_HK_TARGET])
if(!D)
@@ -253,7 +253,7 @@
purpose_fulfilled = TRUE
make_glow()
animate(glow, transform = matrix() * 1.5, alpha = 255, time = 125)
sound_to_playing_players(volume = 100, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/ratvar_rises.ogg')) //End the sounds
sound_to_playing_players('sound/effects/ratvar_rises.ogg', 90, FALSE, channel = CHANNEL_JUSTICAR_ARK)
sleep(125)
make_glow()
animate(glow, transform = matrix() * 3, alpha = 0, time = 5)
@@ -318,19 +318,19 @@
if(-INFINITY to GATEWAY_REEBE_FOUND)
if(!second_sound_played)
sound_to_playing_players('sound/magic/clockwork/invoke_general.ogg', 30, FALSE)
sound_to_playing_players(volume = 10, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
sound_to_playing_players('sound/effects/clockcult_gateway_charging.ogg', 10, FALSE, channel = CHANNEL_JUSTICAR_ARK)
second_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_charging"
if(GATEWAY_REEBE_FOUND to GATEWAY_RATVAR_COMING)
if(!third_sound_played)
sound_to_playing_players(volume = 30, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_active.ogg', TRUE))
sound_to_playing_players('sound/effects/clockcult_gateway_active.ogg', 30, FALSE, channel = CHANNEL_JUSTICAR_ARK)
third_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_active"
if(GATEWAY_RATVAR_COMING to GATEWAY_RATVAR_ARRIVAL)
if(!fourth_sound_played)
sound_to_playing_players(volume = 70, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_closing.ogg', TRUE))
sound_to_playing_players('sound/effects/clockcult_gateway_closing.ogg', 70, FALSE, channel = CHANNEL_JUSTICAR_ARK)
fourth_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_closing"
@@ -260,7 +260,7 @@
if((IS_HERETIC(local_user) || IS_HERETIC_MONSTER(local_user)) && HAS_TRAIT(src,TRAIT_NODROP))
REMOVE_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT)
for(var/mob/living/carbon/human/human_in_range in spiral_range(9,local_user))
for(var/mob/living/carbon/human/human_in_range in viewers(9,local_user))
if(IS_HERETIC(human_in_range) || IS_HERETIC_MONSTER(human_in_range))
continue
+10 -8
View File
@@ -3,21 +3,21 @@
/datum/asset/simple/tgui_common
keep_local_name = TRUE
assets = list(
"tgui-common.bundle.js" = 'tgui/public/tgui-common.bundle.js',
"tgui-common.bundle.js" = file("tgui/public/tgui-common.bundle.js"),
)
/datum/asset/simple/tgui
keep_local_name = TRUE
assets = list(
"tgui.bundle.js" = 'tgui/public/tgui.bundle.js',
"tgui.bundle.css" = 'tgui/public/tgui.bundle.css',
"tgui.bundle.js" = file("tgui/public/tgui.bundle.js"),
"tgui.bundle.css" = file("tgui/public/tgui.bundle.css"),
)
/datum/asset/simple/tgui_panel
keep_local_name = TRUE
assets = list(
"tgui-panel.bundle.js" = 'tgui/public/tgui-panel.bundle.js',
"tgui-panel.bundle.css" = 'tgui/public/tgui-panel.bundle.css',
"tgui-panel.bundle.js" = file("tgui/public/tgui-panel.bundle.js"),
"tgui-panel.bundle.css" = file("tgui/public/tgui-panel.bundle.css"),
)
/datum/asset/simple/headers
@@ -168,10 +168,12 @@
/datum/asset/simple/namespaced/tgfont
assets = list(
"tgfont.eot" = 'tgui/packages/tgfont/dist/tgfont.eot',
"tgfont.woff2" = 'tgui/packages/tgfont/dist/tgfont.woff2',
"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"),
)
parents = list("tgfont.css" = 'tgui/packages/tgfont/dist/tgfont.css')
/datum/asset/spritesheet/chat
name = "chat"
@@ -75,6 +75,9 @@
/obj/machinery/atmospherics/pipe/setPipenet(datum/pipeline/P)
parent = P
/obj/machinery/atmospherics/pipe/zap_act(power, zap_flags)
return 0 // they're not really machines in the normal sense, probably shouldn't explode
/obj/machinery/atmospherics/pipe/Destroy()
QDEL_NULL(parent)
+6
View File
@@ -70,6 +70,12 @@
cost = 1500
contains = list(/obj/item/toy/plush/beeplushie)
/datum/supply_pack/goody/dyespray
name = "Hair Dye Spray"
desc = "A cool spray to dye your hair with awesome colors!"
cost = PAYCHECK_EASY * 2
contains = list(/obj/item/dyespray)
/datum/supply_pack/goody/beach_ball
name = "Beach Ball"
desc = "The simple beach ball is one of Nanotrasen's most popular products. 'Why do we make beach balls? Because we can! (TM)' - Nanotrasen"
+27
View File
@@ -119,6 +119,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/hair_color = "000000" //Hair color
var/facial_hair_style = "Shaved" //Face hair type
var/facial_hair_color = "000000" //Facial hair color
var/grad_style //Hair gradient style
var/grad_color = "FFFFFF" //Hair gradient color
var/skin_tone = "caucasian1" //Skin color
var/use_custom_skin_tone = FALSE
var/left_eye_color = "000000" //Eye color
@@ -505,6 +507,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "<a href='?_src_=prefs;preference=previous_facehair_style;task=input'>&lt;</a> <a href='?_src_=prefs;preference=next_facehair_style;task=input'>&gt;</a><BR>"
dat += "<span style='border: 1px solid #161616; background-color: #[facial_hair_color];'>&nbsp;&nbsp;&nbsp;</span> <a href='?_src_=prefs;preference=facial;task=input'>Change</a><BR>"
dat += "<h3>Hair Gradient</h3>"
dat += "<a style='display:block;width:100px' href='?_src_=prefs;preference=grad_style;task=input'>[grad_style]</a>"
dat += "<a href='?_src_=prefs;preference=previous_grad_style;task=input'>&lt;</a> <a href='?_src_=prefs;preference=next_grad_style;task=input'>&gt;</a><BR>"
dat += "<span style='border: 1px solid #161616; background-color: #[grad_color];'>&nbsp;&nbsp;&nbsp;</span> <a href='?_src_=prefs;preference=grad_color;task=input'>Change</a><BR>"
dat += "</td>"
//Mutant stuff
var/mutant_category = 0
@@ -1711,6 +1719,23 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("previous_facehair_style")
facial_hair_style = previous_list_item(facial_hair_style, GLOB.facial_hair_styles_list)
if("grad_color")
var/new_grad_color = input(user, "Choose your character's gradient colour:", "Character Preference","#"+grad_color) as color|null
if(new_grad_color)
grad_color = sanitize_hexcolor(new_grad_color, 6)
if("grad_style")
var/new_grad_style
new_grad_style = input(user, "Choose your character's hair gradient style:", "Character Preference") as null|anything in GLOB.hair_gradients_list
if(new_grad_style)
grad_style = new_grad_style
if("next_grad_style")
grad_style = next_list_item(grad_style, GLOB.hair_gradients_list)
if("previous_grad_style")
grad_style = previous_list_item(grad_style, GLOB.hair_gradients_list)
if("cycle_bg")
bgstate = next_list_item(bgstate, bgstate_options)
@@ -3016,6 +3041,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
character.dna.skin_tone_override = use_custom_skin_tone ? skin_tone : null
character.hair_style = hair_style
character.facial_hair_style = facial_hair_style
character.grad_style = grad_style
character.grad_color = grad_color
character.underwear = underwear
character.saved_underwear = underwear
@@ -683,6 +683,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["skin_tone"] >> skin_tone
S["hair_style_name"] >> hair_style
S["facial_style_name"] >> facial_hair_style
S["grad_style"] >> grad_style
S["grad_color"] >> grad_color
S["underwear"] >> underwear
S["undie_color"] >> undie_color
S["undershirt"] >> undershirt
@@ -875,6 +877,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
age = sanitize_integer(age, AGE_MIN, AGE_MAX, initial(age))
hair_color = sanitize_hexcolor(hair_color, 6, FALSE)
facial_hair_color = sanitize_hexcolor(facial_hair_color, 6, FALSE)
grad_style = sanitize_inlist(grad_style, GLOB.hair_gradients_list, "None")
grad_color = sanitize_hexcolor(grad_color, 6, FALSE)
eye_type = sanitize_inlist(eye_type, GLOB.eye_types, DEFAULT_EYES_TYPE)
left_eye_color = sanitize_hexcolor(left_eye_color, 6, FALSE)
right_eye_color = sanitize_hexcolor(right_eye_color, 6, FALSE)
@@ -1044,6 +1048,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["skin_tone"] , skin_tone)
WRITE_FILE(S["hair_style_name"] , hair_style)
WRITE_FILE(S["facial_style_name"] , facial_hair_style)
WRITE_FILE(S["grad_style"] , grad_style)
WRITE_FILE(S["grad_color"] , grad_color)
WRITE_FILE(S["underwear"] , underwear)
WRITE_FILE(S["undie_color"] , undie_color)
WRITE_FILE(S["undershirt"] , undershirt)
+2
View File
@@ -83,6 +83,7 @@
icon_state = "clown"
item_state = "clown_hat"
dye_color = "clown"
w_class = WEIGHT_CLASS_SMALL
flags_cover = MASKCOVERSEYES
resistance_flags = FLAMMABLE
actions_types = list(/datum/action/item_action/adjust)
@@ -131,6 +132,7 @@
clothing_flags = ALLOWINTERNALS
icon_state = "mime"
item_state = "mime"
w_class = WEIGHT_CLASS_SMALL
flags_cover = MASKCOVERSEYES
resistance_flags = FLAMMABLE
actions_types = list(/datum/action/item_action/adjust)
+17 -17
View File
@@ -30,7 +30,7 @@
glasses = /obj/item/clothing/glasses/hud/security/sunglasses
back = /obj/item/storage/backpack/captain
belt = /obj/item/storage/belt/security/full
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer=1,\
/obj/item/gun/energy/e_gun=1)
@@ -50,7 +50,7 @@
suit = /obj/item/clothing/suit/space/hardsuit/ert/alert
glasses = /obj/item/clothing/glasses/thermal/eyepatch
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
/obj/item/gun/energy/e_gun=1)
@@ -58,7 +58,7 @@
/datum/outfit/ert/commander/alert/red
name = "ERT Commander - Red Alert"
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
/obj/item/gun/energy/pulse/pistol/loyalpin=1)
@@ -71,7 +71,7 @@
glasses = /obj/item/clothing/glasses/hud/security/sunglasses
belt = /obj/item/storage/belt/security/full
back = /obj/item/storage/backpack/security
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/storage/box/handcuffs=1,\
/obj/item/clothing/mask/gas/sechailer=1,\
/obj/item/gun/energy/e_gun/stun=1,\
@@ -91,7 +91,7 @@
name = "ERT Security - Amber Alert"
suit = /obj/item/clothing/suit/space/hardsuit/ert/alert/sec
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/storage/box/handcuffs=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
/obj/item/melee/baton/loaded=1,\
@@ -99,7 +99,7 @@
/datum/outfit/ert/security/alert/red
name = "ERT Security - Red Alert"
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/storage/box/handcuffs=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
/obj/item/melee/baton/loaded=1,\
@@ -114,7 +114,7 @@
back = /obj/item/storage/backpack/satchel/med
belt = /obj/item/storage/belt/medical
r_hand = /obj/item/storage/firstaid/regular
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer=1,\
/obj/item/gun/energy/e_gun=1,\
@@ -135,7 +135,7 @@
name = "ERT Medic - Amber Alert"
suit = /obj/item/clothing/suit/space/hardsuit/ert/alert/med
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
/obj/item/gun/energy/e_gun=1,\
@@ -144,7 +144,7 @@
/datum/outfit/ert/medic/alert/red
name = "ERT Medic - Red Alert"
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
/obj/item/gun/energy/pulse/pistol/loyalpin=1,\
@@ -161,7 +161,7 @@
belt = /obj/item/storage/belt/utility/full
l_pocket = /obj/item/rcd_ammo/large
r_hand = /obj/item/storage/firstaid/regular
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer=1,\
/obj/item/gun/energy/e_gun=1,\
@@ -181,7 +181,7 @@
name = "ERT Engineer - Amber Alert"
suit = /obj/item/clothing/suit/space/hardsuit/ert/alert/engi
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
/obj/item/gun/energy/e_gun=1,\
@@ -189,7 +189,7 @@
/datum/outfit/ert/engineer/alert/red
name = "ERT Engineer - Red Alert"
backpack_contents = list(/obj/item/storage/box/engineer=1,\
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\
/obj/item/melee/baton/loaded=1,\
/obj/item/clothing/mask/gas/sechailer/swat=1,\
/obj/item/gun/energy/pulse/pistol/loyalpin=1,\
@@ -260,7 +260,7 @@
name = "Inquisition Commander"
r_hand = /obj/item/nullrod/scythe/talking/chainsword
suit = /obj/item/clothing/suit/space/hardsuit/ert/paranormal
backpack_contents = list(/obj/item/storage/box/engineer=1,
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,
/obj/item/clothing/mask/gas/sechailer=1,
/obj/item/gun/energy/e_gun=1)
@@ -269,7 +269,7 @@
suit = /obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor
backpack_contents = list(/obj/item/storage/box/engineer=1,
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,
/obj/item/storage/box/handcuffs=1,
/obj/item/clothing/mask/gas/sechailer=1,
/obj/item/gun/energy/e_gun/stun=1,
@@ -281,7 +281,7 @@
suit = /obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor
backpack_contents = list(/obj/item/storage/box/engineer=1,
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,
/obj/item/melee/baton/loaded=1,
/obj/item/clothing/mask/gas/sechailer=1,
/obj/item/gun/energy/e_gun=1,
@@ -307,7 +307,7 @@
glasses = /obj/item/clothing/glasses/hud/health
back = /obj/item/storage/backpack/cultpack
belt = /obj/item/storage/belt/soulstone
backpack_contents = list(/obj/item/storage/box/engineer=1,
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,
/obj/item/nullrod=1,
/obj/item/clothing/mask/gas/sechailer=1,
/obj/item/gun/energy/e_gun=1,
@@ -319,7 +319,7 @@
suit = /obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor
belt = /obj/item/storage/belt/soulstone/full/chappy
backpack_contents = list(/obj/item/storage/box/engineer=1,
backpack_contents = list(/obj/item/storage/box/survival/engineer=1,
/obj/item/grenade/chem_grenade/holy=1,
/obj/item/nullrod=1,
/obj/item/clothing/mask/gas/sechailer=1,
+1 -1
View File
@@ -28,7 +28,7 @@
id = /obj/item/card/id/syndicate/locked_banking
belt = /obj/item/gun/ballistic/automatic/pistol
l_pocket = /obj/item/paper/fluff/vr/fluke_ops
backpack_contents = list(/obj/item/storage/box/syndie=1,\
backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\
/obj/item/kitchen/knife/combat/survival)
starting_funds = 0 //Should be operating, not shopping.
@@ -237,12 +237,28 @@
/obj/item/clothing/head/helmet/space/hardsuit/mining/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
/obj/item/clothing/head/helmet/space/hardsuit/mining/proc/upgrade_icon(datum/source, amount, maxamount)
SIGNAL_HANDLER
if(amount)
name = "reinforced [initial(name)]"
hardsuit_type = "mining_goliath"
if(amount == maxamount)
hardsuit_type = "mining_goliath_full"
icon_state = "hardsuit[on]-[hardsuit_type]"
if(ishuman(loc))
var/mob/living/carbon/human/wearer = loc
if(wearer.head == src)
wearer.update_inv_head()
/obj/item/clothing/suit/space/hardsuit/mining
icon_state = "hardsuit-mining"
name = "mining hardsuit"
desc = "A special suit that protects against hazardous, low pressure environments. Has reinforced plating for wildlife encounters."
item_state = "mining_hardsuit"
hardsuit_type = "mining"
max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF
armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 75, "wound" = 15)
@@ -254,6 +270,21 @@
/obj/item/clothing/suit/space/hardsuit/mining/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
/obj/item/clothing/suit/space/hardsuit/mining/proc/upgrade_icon(datum/source, amount, maxamount)
SIGNAL_HANDLER
if(amount)
name = "reinforced [initial(name)]"
hardsuit_type = "mining_goliath"
if(amount == maxamount)
hardsuit_type = "mining_goliath_full"
icon_state = "hardsuit-[hardsuit_type]"
if(ishuman(loc))
var/mob/living/carbon/human/wearer = loc
if(wearer.wear_suit == src)
wearer.update_inv_wear_suit()
//Syndicate hardsuit
/obj/item/clothing/head/helmet/space/hardsuit/syndi
+36 -3
View File
@@ -136,17 +136,24 @@
icon_state = "syndievest"
mutantrace_variation = STYLE_DIGITIGRADE
/obj/item/clothing/suit/armor/vest/capcarapace/alt
/obj/item/clothing/suit/toggle/captains_parade
name = "captain's parade jacket"
desc = "For when an armoured vest isn't fashionable enough."
icon_state = "capformal"
item_state = "capspacesuit"
body_parts_covered = CHEST|GROIN|ARMS
armor = list("melee" = 50, "bullet" = 40, "laser" = 50, "energy" = 50, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 90, "wound" = 10)
togglename = "buttons"
/obj/item/clothing/suit/toggle/captains_parade/Initialize()
. = ..()
allowed = GLOB.security_wintercoat_allowed
/obj/item/clothing/suit/armor/riot
name = "riot suit"
desc = "A suit of semi-flexible polycarbonate body armor with heavy padding to protect against melee attacks. Helps the wearer resist shoving in close quarters."
icon_state = "swat"
item_state = "swat_suit"
icon_state = "riot"
item_state = "riot"
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
@@ -319,3 +326,29 @@
cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
armor = list("melee" = 25, "bullet" = 20, "laser" = 20, "energy" = 10, "bomb" = 20, "bio" = 50, "rad" = 20, "fire" = -10, "acid" = 50, "wound" = 10)
/obj/item/clothing/suit/toggle/armor/vest/centcom_formal
name = "\improper CentCom formal coat"
desc = "A stylish coat given to CentCom Commanders. Perfect for sending ERTs to suicide missions with style!"
icon_state = "centcom_formal"
item_state = "centcom"
body_parts_covered = CHEST|GROIN|ARMS
armor = list("melee" = 35, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 35, "bio" = 10, "rad" = 10, "fire" = 10, "acid" = 60)
togglename = "buttons"
/obj/item/clothing/suit/toggle/armor/vest/centcom_formal/Initialize()
. = ..()
allowed = GLOB.security_wintercoat_allowed
/obj/item/clothing/suit/toggle/armor/hos/hos_formal
name = "\improper Head of Security's parade jacket"
desc = "For when an armoured vest isn't fashionable enough."
icon_state = "hosformal"
item_state = "hostrench"
body_parts_covered = CHEST|GROIN|ARMS
armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 90, "wound" = 10)
togglename = "buttons"
/obj/item/clothing/suit/toggle/armor/hos/hos_formal/Initialize()
. = ..()
allowed = GLOB.security_wintercoat_allowed
+3
View File
@@ -6,6 +6,7 @@
var/hoodtype = /obj/item/clothing/head/hooded/winterhood //so the chaplain hoodie or other hoodies can override this
///Alternative mode for hiding the hood, instead of storing the hood in the suit it qdels it, useful for when you deal with hooded suit with storage.
var/alternative_mode = FALSE
var/no_t //do not update sprites when pulling up hood so we can avoid oddities with certain mechanics
/obj/item/clothing/suit/hooded/Initialize()
. = ..()
@@ -51,6 +52,8 @@
update_icon()
/obj/item/clothing/suit/hooded/update_icon_state()
if(no_t)
return
icon_state = "[initial(icon_state)]"
if(ishuman(hood?.loc))
var/mob/living/carbon/human/H = hood.loc
@@ -8,8 +8,6 @@
#define CONE_WAFFLE 8
#define CONE_CHOC 9
/obj/machinery/icecream_vat
name = "ice cream vat"
desc = "Ding-aling ding dong. Get your Nanotrasen-approved ice cream!"
@@ -35,6 +33,8 @@
/datum/reagent/consumable/ethanol/singulo = 6,
/datum/reagent/consumable/peachjuice = 6,
/datum/reagent/consumable/grapejuice = 6)
var/custom_taste
var/custom_color
/obj/machinery/icecream_vat/proc/get_ingredient_list(type)
switch(type)
@@ -99,7 +99,10 @@
dat += "<b>Peach ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_PEACH]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_PEACH];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_PEACH];amount=5'><b>x5</b></a> [product_types[ICECREAM_PEACH]] dollops left. (Ingredients: milk, ice, peach juice)<br>"
dat += "<b>Grape ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_GRAPE]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_GRAPE];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_GRAPE];amount=5'><b>x5</b></a> [product_types[ICECREAM_GRAPE]] dollops left. (Ingredients: milk, ice, grape juice)<br>"
dat += "<b>Blue ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_BLUE]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_BLUE];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_BLUE];amount=5'><b>x5</b></a> [product_types[ICECREAM_BLUE]] dollops left. (Ingredients: milk, ice, singulo)<br>"
dat += "<b>Custom ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_CUSTOM]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_CUSTOM];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_CUSTOM];amount=5'><b>x5</b></a> [product_types[ICECREAM_CUSTOM]] dollops left. (Ingredients: milk, ice, optional flavoring)<br></div>"
dat += "<b>Custom ice cream:</b> <a href='?src=[REF(src)];select=[ICECREAM_CUSTOM]'><b>Select</b></a> <a href='?src=[REF(src)];make=[ICECREAM_CUSTOM];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[ICECREAM_CUSTOM];amount=5'><b>x5</b></a> [product_types[ICECREAM_CUSTOM]] dollops left. (Ingredients: milk, ice, optional flavoring)<br>"
dat += "<a href='?src=[REF(src)];custom_taste=1;'><b>Change custom taste: [custom_taste ? custom_taste : "Default"]</b></a>"
dat += "<br><a href='?src=[REF(src)];custom_color=1;'><b>Change custom color: [custom_color ? custom_color : "Default"]</b></a>"
dat += "<br><a href='?src=[REF(src)];reset_custom=1;'><b>Reset custom ice cream taste and color to defaults</b></a></div>"
dat += "<br><b>CONES</b><br><div class='statusDisplay'>"
dat += "<b>Waffle cones:</b> <a href='?src=[REF(src)];cone=[CONE_WAFFLE]'><b>Dispense</b></a> <a href='?src=[REF(src)];make=[CONE_WAFFLE];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[CONE_WAFFLE];amount=5'><b>x5</b></a> [product_types[CONE_WAFFLE]] cones left. (Ingredients: flour, sugar)<br>"
dat += "<b>Chocolate cones:</b> <a href='?src=[REF(src)];cone=[CONE_CHOC]'><b>Dispense</b></a> <a href='?src=[REF(src)];make=[CONE_CHOC];amount=1'><b>Make</b></a> <a href='?src=[REF(src)];make=[CONE_CHOC];amount=5'><b>x5</b></a> [product_types[CONE_CHOC]] cones left. (Ingredients: flour, sugar, coco powder)<br></div>"
@@ -128,7 +131,7 @@
visible_message("[icon2html(src, viewers(src))] <span class='info'>[user] scoops delicious [flavour_name] ice cream into [I].</span>")
product_types[dispense_flavour] -= 1
if(beaker && beaker.reagents.total_volume)
I.add_ice_cream(flavour_name, beaker.reagents)
I.add_ice_cream(flavour_name, beaker.reagents, custom_color, custom_taste)
else
I.add_ice_cream(flavour_name)
if(I.reagents.total_volume < 10)
@@ -213,14 +216,25 @@
if(href_list["refill"])
RefillFromBeaker()
updateDialog()
if(href_list["refresh"])
updateDialog()
if(href_list["close"])
usr.unset_machine()
usr << browse(null,"window=icecreamvat")
if(href_list["custom_taste"])
custom_taste = stripped_input(usr, "Set a custom taste for the custom icecream. 50 characters max, leave blank to go back to the default option.", max_length = 50)
if(href_list["custom_color"])
custom_color = input(usr, "Choose a color for the custom icecream. Cancel to go back to the default option.") as color|null
if(href_list["reset_custom"])
custom_taste = null
custom_color = null
updateDialog() // i have no clue why we even have refresh when this is a thing but sure
return
/obj/item/reagent_containers/food/snacks/icecream
@@ -251,7 +265,7 @@
desc = "Delicious [cone_name] cone, but no ice cream."
/obj/item/reagent_containers/food/snacks/icecream/proc/add_ice_cream(flavour_name, datum/reagents/R)
/obj/item/reagent_containers/food/snacks/icecream/proc/add_ice_cream(flavour_name, datum/reagents/R, custom_color, custom_taste)
name = "[flavour_name] icecream"
switch (flavour_name) // adding the actual reagents advertised in the ingredient list
if ("vanilla")
@@ -286,8 +300,11 @@
if(R && R.total_volume >= 4) //consumable reagents have stronger taste so higher volume will allow non-food flavourings to break through better.
var/mutable_appearance/flavoring = mutable_appearance(icon,"icecream_custom")
var/datum/reagent/master = R.get_master_reagent()
flavoring.color = master.color
filling_color = master.color
flavoring.color = custom_color ? custom_color : master.color
filling_color = custom_color ? custom_color : master.color
if(custom_taste)
tastes = list("[custom_taste]" = 1)
reagents.force_alt_taste = TRUE
name = "[master.name] icecream"
desc = "A delicious [cone_type] cone filled with artisanal icecream. Made with real [master.name]. Ain't that something."
R.trans_to(src, 4)
+12
View File
@@ -641,6 +641,18 @@ Since Ramadan is an entire month that lasts 29.5 days on average, the start and
/datum/holiday/easter/getStationPrefix()
return pick("Fluffy","Bunny","Easter","Egg")
/datum/holiday/ianbirthday
name = "Ian's Birthday" //github.com/tgstation/tgstation/commit/de7e4f0de0d568cd6e1f0d7bcc3fd34700598acb
begin_month = SEPTEMBER
begin_day = 9
end_day = 10
/datum/holiday/ianbirthday/greet()
return "Happy birthday, Ian!"
/datum/holiday/ianbirthday/getStationPrefix()
return pick("Ian", "Corgi", "Erro")
//Random citadel thing for halloween species
/proc/force_enable_halloween_species()
var/list/oldlist = SSevents.holidays
@@ -39,7 +39,7 @@
backpack = /obj/item/storage/backpack/industrial
satchel = /obj/item/storage/backpack/satchel/eng
duffelbag = /obj/item/storage/backpack/duffelbag/engineering
box = /obj/item/storage/box/engineer
box = /obj/item/storage/box/survival/engineer
pda_slot = SLOT_L_STORE
backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
@@ -54,7 +54,7 @@
backpack = /obj/item/storage/backpack/industrial
satchel = /obj/item/storage/backpack/satchel/eng
duffelbag = /obj/item/storage/backpack/duffelbag/engineering
box = /obj/item/storage/box/engineer
box = /obj/item/storage/box/survival/engineer
pda_slot = SLOT_L_STORE
chameleon_extras = /obj/item/stamp/ce
@@ -58,7 +58,7 @@
backpack = /obj/item/storage/backpack/security
satchel = /obj/item/storage/backpack/satchel/sec
duffelbag = /obj/item/storage/backpack/duffelbag/sec
box = /obj/item/storage/box/security
box = /obj/item/storage/box/survival/security
implants = list(/obj/item/implant/mindshield)
@@ -133,7 +133,7 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S
backpack = /obj/item/storage/backpack/security
satchel = /obj/item/storage/backpack/satchel/sec
duffelbag = /obj/item/storage/backpack/duffelbag/sec
box = /obj/item/storage/box/security
box = /obj/item/storage/box/survival/security
implants = list(/obj/item/implant/mindshield)
+1 -1
View File
@@ -45,7 +45,7 @@
backpack = /obj/item/storage/backpack/explorer
satchel = /obj/item/storage/backpack/satchel/explorer
duffelbag = /obj/item/storage/backpack/duffelbag
box = /obj/item/storage/box/survival_mining
box = /obj/item/storage/box/survival/mining
chameleon_extras = /obj/item/gun/energy/kinetic_accelerator
@@ -42,7 +42,7 @@
backpack = /obj/item/storage/backpack/industrial
satchel = /obj/item/storage/backpack/satchel/eng
duffelbag = /obj/item/storage/backpack/duffelbag/engineering
box = /obj/item/storage/box/engineer
box = /obj/item/storage/box/survival/engineer
pda_slot = SLOT_L_STORE
backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
+1 -1
View File
@@ -54,7 +54,7 @@
backpack = /obj/item/storage/backpack/security
satchel = /obj/item/storage/backpack/satchel/sec
duffelbag = /obj/item/storage/backpack/duffelbag/sec
box = /obj/item/storage/box/security
box = /obj/item/storage/box/survival/security
implants = list(/obj/item/implant/mindshield)
@@ -8,61 +8,73 @@
..()
return late ? INITIALIZE_HINT_LATELOAD : INITIALIZE_HINT_QDEL
//airlock helpers
/obj/effect/mapping_helpers/airlock
layer = DOOR_HELPER_LAYER
/obj/effect/mapping_helpers/airlock/Initialize(mapload)
. = ..()
if(!mapload)
log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(!airlock)
log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
else
payload(airlock)
/obj/effect/mapping_helpers/airlock/proc/payload(obj/machinery/door/airlock/payload)
return
/obj/effect/mapping_helpers/airlock/cyclelink_helper
name = "airlock cyclelink helper"
icon_state = "airlock_cyclelink_helper"
/obj/effect/mapping_helpers/airlock/cyclelink_helper/Initialize(mapload)
. = ..()
if(!mapload)
log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
if(airlock.cyclelinkeddir)
log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!")
else
airlock.cyclelinkeddir = dir
/obj/effect/mapping_helpers/airlock/cyclelink_helper/payload(obj/machinery/door/airlock/airlock)
if(airlock.cyclelinkeddir)
log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!")
else
log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
airlock.cyclelinkeddir = dir
/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi
name = "airlock multi-cyclelink helper"
icon_state = "airlock_multicyclelink_helper"
var/cycle_id
/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi/payload(obj/machinery/door/airlock/airlock)
if(airlock.closeOtherId)
log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] closeOtherId, but it's already set!")
else
airlock.closeOtherId = cycle_id
/obj/effect/mapping_helpers/airlock/locked
name = "airlock lock helper"
icon_state = "airlock_locked_helper"
/obj/effect/mapping_helpers/airlock/locked/Initialize(mapload)
. = ..()
if(!mapload)
log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
if(airlock.locked)
log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!")
else
airlock.locked = TRUE
/obj/effect/mapping_helpers/airlock/locked/payload(obj/machinery/door/airlock/airlock)
if(airlock.locked)
log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!")
else
log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
airlock.locked = TRUE
/obj/effect/mapping_helpers/airlock/unres
name = "airlock unresctricted side helper"
icon_state = "airlock_unres_helper"
/obj/effect/mapping_helpers/airlock/unres/Initialize(mapload)
. = ..()
if(!mapload)
log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
airlock.unres_sides ^= dir
/obj/effect/mapping_helpers/airlock/unres/payload(obj/machinery/door/airlock/airlock)
airlock.unres_sides ^= dir
/obj/effect/mapping_helpers/airlock/abandoned
name = "airlock abandoned helper"
icon_state = "airlock_abandoned"
/obj/effect/mapping_helpers/airlock/abandoned/payload(obj/machinery/door/airlock/airlock)
if(airlock.abandoned)
log_mapping("[src] at [AREACOORD(src)] tried to make [airlock] abandoned but it's already abandoned!")
else
log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
airlock.abandoned = TRUE
//needs to do its thing before spawn_rivers() is called
@@ -79,9 +91,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
//This helper applies components to things on the map directly.
/obj/effect/mapping_helpers/component_injector
name = "Component Injector"
icon_state = "component"
late = TRUE
var/target_type
var/target_name
var/all = FALSE //Will inject into all fitting the criteria if true, otherwise first found
var/target_type //Will inject into atoms of this type
var/target_name //Will inject into atoms with this name
var/component_type
//Late init so everything is likely ready and loaded (no warranty)
@@ -98,8 +112,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
continue
var/cargs = build_args()
A._AddComponent(cargs)
if(!all)
qdel(src)
return
if(all)
qdel(src)
return
/obj/effect/mapping_helpers/component_injector/proc/build_args()
return list(component_type)
@@ -115,3 +132,276 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
CRASH("Wrong disease type passed in.")
var/datum/disease/D = new disease_type()
return list(component_type,D)
// /obj/effect/mapping_helpers/component_injector/areabound
// name = "Areabound Injector"
// icon_state = "component_areabound"
// component_type = /datum/component/areabound
// target_type = /atom/movable
/obj/effect/mapping_helpers/dead_body_placer
name = "Dead Body placer"
late = TRUE
icon_state = "deadbodyplacer"
var/bodycount = 2 //number of bodies to spawn
/obj/effect/mapping_helpers/dead_body_placer/LateInitialize()
var/area/a = get_area(src)
var/list/trays = list()
for (var/i in a.contents)
if (istype(i, /obj/structure/bodycontainer/morgue))
trays += i
if(!trays.len)
log_mapping("[src] at [x],[y] could not find any morgues.")
return
for (var/i = 1 to bodycount)
var/obj/structure/bodycontainer/morgue/j = pick(trays)
var/mob/living/carbon/human/h = new /mob/living/carbon/human(j, 1)
h.death()
for (var/part in h.internal_organs) //randomly remove organs from each body, set those we keep to be in stasis
if (prob(40))
qdel(part)
else
var/obj/item/organ/O = part
O.organ_flags |= ORGAN_FROZEN
j.update_appearance()
qdel(src)
//On Ian's birthday, the hop's office is decorated.
/obj/effect/mapping_helpers/ianbirthday
name = "Ian's Bday Helper"
late = TRUE
icon_state = "iansbdayhelper"
var/balloon_clusters = 2
/obj/effect/mapping_helpers/ianbirthday/LateInitialize()
if(locate(/datum/holiday/ianbirthday) in SSevents.holidays)
birthday()
qdel(src)
/obj/effect/mapping_helpers/ianbirthday/proc/birthday()
var/area/a = get_area(src)
var/list/table = list()//should only be one aka the front desk, but just in case...
var/list/openturfs = list()
//confetti and a corgi balloon! (and some list stuff for more decorations)
for(var/thing in a.contents)
if(istype(thing, /obj/structure/table/reinforced))
table += thing
if(isopenturf(thing))
// new /obj/effect/decal/cleanable/confetti(thing)
// if(locate(/obj/structure/bed/dogbed/ian) in thing)
// new /obj/item/toy/balloon/corgi(thing)
// else
openturfs += thing
//cake + knife to cut it!
if(length(table))
var/turf/food_turf = get_turf(pick(table))
new /obj/item/kitchen/knife(food_turf)
var/obj/item/reagent_containers/food/snacks/store/cake/birthday/iancake = new(food_turf)
iancake.desc = "Happy birthday, Ian!"
// remind me to give ian proper baloons!
//some balloons! this picks an open turf and pops a few balloons in and around that turf, yay.
// for(var/i in 1 to balloon_clusters)
// var/turf/clusterspot = pick_n_take(openturfs)
// new /obj/item/toy/balloon(clusterspot)
// var/balloons_left_to_give = 3 //the amount of balloons around the cluster
// var/list/dirs_to_balloon = GLOB.cardinals.Copy()
// while(balloons_left_to_give > 0)
// balloons_left_to_give--
// var/chosen_dir = pick_n_take(dirs_to_balloon)
// var/turf/balloonstep = get_step(clusterspot, chosen_dir)
// var/placed = FALSE
// if(isopenturf(balloonstep))
// var/obj/item/toy/balloon/B = new(balloonstep)//this clumps the cluster together
// placed = TRUE
// if(chosen_dir == NORTH)
// B.pixel_y -= 10
// if(chosen_dir == SOUTH)
// B.pixel_y += 10
// if(chosen_dir == EAST)
// B.pixel_x -= 10
// if(chosen_dir == WEST)
// B.pixel_x += 10
// if(!placed)
// new /obj/item/toy/balloon(clusterspot)
//remind me to add wall decor!
/obj/effect/mapping_helpers/ianbirthday/admin//so admins may birthday any room
name = "generic birthday setup"
icon_state = "bdayhelper"
/obj/effect/mapping_helpers/ianbirthday/admin/LateInitialize()
birthday()
qdel(src)
//Ian, like most dogs, loves a good new years eve party.
/obj/effect/mapping_helpers/iannewyear
name = "Ian's New Years Helper"
late = TRUE
icon_state = "iansnewyrshelper"
/obj/effect/mapping_helpers/iannewyear/LateInitialize()
if(SSevents.holidays && SSevents.holidays[NEW_YEAR])
fireworks()
qdel(src)
/obj/effect/mapping_helpers/iannewyear/proc/fireworks()
var/area/a = get_area(src)
var/list/table = list()//should only be one aka the front desk, but just in case...
var/list/openturfs = list()
for(var/thing in a.contents)
if(istype(thing, /obj/structure/table/reinforced))
table += thing
else if(isopenturf(thing))
if(locate(/obj/structure/bed/dogbed/ian) in thing)
new /obj/item/clothing/head/festive(thing)
var/obj/item/reagent_containers/food/drinks/bottle/champagne/iandrink = new(thing)
iandrink.name = "dog champagne"
iandrink.pixel_y += 8
iandrink.pixel_x += 8
else
openturfs += thing
var/turf/fireworks_turf = get_turf(pick(table))
var/obj/item/storage/box/matches/matchbox = new(fireworks_turf)
matchbox.pixel_y += 8
matchbox.pixel_x -= 3
// new /obj/item/storage/box/fireworks/dangerous(fireworks_turf) //dangerous version for extra holiday memes.
//lets mappers place notes on airlocks with custom info or a pre-made note from a path
/obj/effect/mapping_helpers/airlock_note_placer
name = "Airlock Note Placer"
late = TRUE
icon_state = "airlocknoteplacer"
var/note_info //for writing out custom notes without creating an extra paper subtype
var/note_name //custom note name
var/note_path //if you already have something wrote up in a paper subtype, put the path here
/obj/effect/mapping_helpers/airlock_note_placer/LateInitialize()
var/turf/turf = get_turf(src)
if(note_path && !istype(note_path, /obj/item/paper)) //don't put non-paper in the paper slot thank you
log_mapping("[src] at [x],[y] had an improper note_path path, could not place paper note.")
qdel(src)
if(locate(/obj/machinery/door/airlock) in turf)
var/obj/machinery/door/airlock/found_airlock = locate(/obj/machinery/door/airlock) in turf
if(note_path)
found_airlock.note = note_path
found_airlock.update_appearance()
qdel(src)
if(note_info)
var/obj/item/paper/paper = new /obj/item/paper(src)
if(note_name)
paper.name = note_name
paper.info = "[note_info]"
found_airlock.note = paper
paper.forceMove(found_airlock)
found_airlock.update_appearance()
qdel(src)
log_mapping("[src] at [x],[y] had no note_path or note_info, cannot place paper note.")
qdel(src)
log_mapping("[src] at [x],[y] could not find an airlock on current turf, cannot place paper note.")
qdel(src)
//This helper applies traits to things on the map directly.
/obj/effect/mapping_helpers/trait_injector
name = "Trait Injector"
icon_state = "trait"
late = TRUE
///Will inject into all fitting the criteria if false, otherwise first found.
var/first_match_only = TRUE
///Will inject into atoms of this type.
var/target_type
///Will inject into atoms with this name.
var/target_name
///Name of the trait, in the lower-case text (NOT the upper-case define) form.
var/trait_name
//Late init so everything is likely ready and loaded (no warranty)
/obj/effect/mapping_helpers/trait_injector/LateInitialize()
if(!GLOB.trait_name_map)
GLOB.trait_name_map = generate_trait_name_map()
if(!GLOB.trait_name_map.Find(trait_name))
CRASH("Wrong trait in [type] - [trait_name] is not a trait")
var/turf/target_turf = get_turf(src)
var/matches_found = 0
for(var/a in target_turf.GetAllContents())
var/atom/atom_on_turf = a
if(atom_on_turf == src)
continue
if(target_name && atom_on_turf.name != target_name)
continue
if(target_type && !istype(atom_on_turf,target_type))
continue
ADD_TRAIT(atom_on_turf, trait_name, MAPPING_HELPER_TRAIT)
matches_found++
if(first_match_only)
qdel(src)
return
if(!matches_found)
stack_trace("Trait mapper found no targets at ([x], [y], [z]). First Match Only: [first_match_only ? "true" : "false"] target type: [target_type] | target name: [target_name] | trait name: [trait_name]")
qdel(src)
/// Fetches an external dmi and applies to the target object
/obj/effect/mapping_helpers/custom_icon
name = "Custom Icon Helper"
icon_state = "trait"
late = TRUE
///Will inject into all fitting the criteria if false, otherwise first found.
var/first_match_only = TRUE
///Will inject into atoms of this type.
var/target_type
///Will inject into atoms with this name.
var/target_name
/// This is the var tha will be set with the fetched icon. In case you want to set some secondary icon sheets like inhands and such.
var/target_variable = "icon"
/// This should return raw dmi in response to http get request. For example: "https://github.com/tgstation/SS13-sprites/raw/master/mob/medu.dmi?raw=true"
var/icon_url
/obj/effect/mapping_helpers/custom_icon/LateInitialize()
///TODO put this injector stuff under common root
var/I = fetch_icon(icon_url)
var/turf/target_turf = get_turf(src)
var/matches_found = 0
for(var/a in target_turf.GetAllContents())
var/atom/atom_on_turf = a
if(atom_on_turf == src)
continue
if(target_name && atom_on_turf.name != target_name)
continue
if(target_type && !istype(atom_on_turf,target_type))
continue
atom_on_turf.vars[target_variable] = I
matches_found++
if(first_match_only)
qdel(src)
return
if(!matches_found)
stack_trace("[src] found no targets at ([x], [y], [z]). First Match Only: [first_match_only ? "true" : "false"] target type: [target_type] | target name: [target_name]")
qdel(src)
/obj/effect/mapping_helpers/custom_icon/proc/fetch_icon(url)
var/static/icon_cache = list()
var/static/query_in_progress = FALSE //We're using a single tmp file so keep it linear.
if(query_in_progress)
UNTIL(!query_in_progress)
if(icon_cache[url])
return icon_cache[url]
log_asset("Custom Icon Helper fetching dmi from: [url]")
var/datum/http_request/request = new()
var/file_name = "tmp/custom_map_icon.dmi"
request.prepare(RUSTG_HTTP_METHOD_GET, url , "", "", file_name)
query_in_progress = TRUE
request.begin_async()
UNTIL(request.is_complete())
var/datum/http_response/response = request.into_response()
if(response.errored || response.status_code != 200)
query_in_progress = FALSE
CRASH("Failed to fetch mapped custom icon from url [url], code: [response.status_code], error: [response.error]")
var/icon/I = new(file_name)
icon_cache[url] = I
query_in_progress = FALSE
return I
@@ -30,8 +30,8 @@
qdel(src)
/obj/effect/baseturf_helper/proc/replace_baseturf(turf/thing)
var/list/baseturf_cache = thing.baseturfs
if(length(baseturf_cache))
if(length(thing.baseturfs))
var/list/baseturf_cache = thing.baseturfs.Copy()
for(var/i in baseturf_cache)
if(baseturf_to_replace[i])
baseturf_cache -= i
@@ -44,6 +44,8 @@
else
thing.PlaceOnBottom(null, baseturf)
/obj/effect/baseturf_helper/space
name = "space baseturf editor"
baseturf = /turf/open/space
@@ -79,4 +81,3 @@
/obj/effect/baseturf_helper/lava_land/surface
name = "lavaland baseturf editor"
baseturf = /turf/open/lava/smooth/lava_land_surface
+38 -3
View File
@@ -2,8 +2,9 @@
/obj/item/clothing/suit/hooded/explorer
name = "explorer suit"
desc = "An armoured suit for exploring harsh environments."
icon_state = "explorer"
item_state = "explorer"
icon_state = "explorer-normal"
item_state = "explorer-normal"
var/suit_type = "normal"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
cold_protection = CHEST|GROIN|LEGS|ARMS
min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
@@ -13,11 +14,15 @@
allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator, /obj/item/pickaxe)
resistance_flags = FIRE_PROOF
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC
no_t = TRUE
/obj/item/clothing/head/hooded/explorer
name = "explorer hood"
desc = "An armoured hood for exploring harsh environments."
icon_state = "explorer"
icon_state = "explorer-normal"
item_state = "explorer-normal"
var/suit_type = "normal"
var/basestate = "normal"
body_parts_covered = HEAD
flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS
min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT
@@ -33,10 +38,40 @@
/obj/item/clothing/suit/hooded/explorer/standard/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
/obj/item/clothing/suit/hooded/explorer/standard/proc/upgrade_icon(datum/source, amount, maxamount)
SIGNAL_HANDLER
if(amount)
name = "reinforced [initial(name)]"
suit_type = "normal_goliath"
if(amount == maxamount)
suit_type = "normal_goliath_full"
icon_state = "explorer-[suit_type]"
if(ishuman(loc))
var/mob/living/carbon/human/wearer = loc
if(wearer.wear_suit == src)
wearer.update_inv_wear_suit()
/obj/item/clothing/head/hooded/explorer/standard/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
/obj/item/clothing/head/hooded/explorer/standard/proc/upgrade_icon(datum/source, amount, maxamount)
SIGNAL_HANDLER
if(amount)
name = "reinforced [initial(name)]"
suit_type = "normal_goliath"
if(amount == maxamount)
suit_type = "normal_goliath_full"
icon_state = "explorer-[suit_type]"
if(ishuman(loc))
var/mob/living/carbon/human/wearer = loc
if(wearer.head == src)
wearer.update_inv_head()
/obj/item/clothing/mask/gas/explorer
name = "explorer gas mask"
@@ -1058,3 +1058,58 @@
/datum/sprite_accessory/hair/zone
name = "Zone"
icon_state = "hair_zone"
/datum/sprite_accessory/hair_gradient
icon = 'icons/mob/hair_gradients.dmi'
/datum/sprite_accessory/hair_gradient/none
name = "None"
icon_state = "none"
/datum/sprite_accessory/hair_gradient/fadeup
name = "Fade Up"
icon_state = "fadeup"
/datum/sprite_accessory/hair_gradient/fadedown
name = "Fade Down"
icon_state = "fadedown"
/datum/sprite_accessory/hair_gradient/vertical_split
name = "Vertical Split"
icon_state = "vsplit"
/datum/sprite_accessory/hair_gradient/_split
name = "Horizontal Split"
icon_state = "bottomflat"
/datum/sprite_accessory/hair_gradient/reflected
name = "Reflected"
icon_state = "reflected_high"
/datum/sprite_accessory/hair_gradient/reflected_inverse
name = "Reflected Inverse"
icon_state = "reflected_inverse_high"
/datum/sprite_accessory/hair_gradient/wavy
name = "Wavy"
icon_state = "wavy"
/datum/sprite_accessory/hair_gradient/long_fade_up
name = "Long Fade Up"
icon_state = "long_fade_up"
/datum/sprite_accessory/hair_gradient/long_fade_down
name = "Long Fade Down"
icon_state = "long_fade_down"
/datum/sprite_accessory/hair_gradient/short_fade_up
name = "Short Fade Up"
icon_state = "short_fade_up"
/datum/sprite_accessory/hair_gradient/short_fade_down
name = "Short Fade Down"
icon_state = "short_fade_down"
/datum/sprite_accessory/hair_gradient/wavy_spike
name = "Spiked Wavy"
icon_state = "wavy_spiked"
@@ -20,6 +20,11 @@
var/hair_color = "000"
var/hair_style = "Bald"
///Colour used for the hair gradient.
var/grad_color = "000"
///Style used for the hair gradient.
var/grad_style
//Facial hair colour and style
var/facial_hair_color = "000"
var/facial_hair_style = "Shaved"
@@ -52,6 +52,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/hair_color
///The alpha used by the hair. 255 is completely solid, 0 is invisible.
var/hair_alpha = 255
///The gradient style used for the mob's hair.
var/grad_style
///The gradient color used to color the gradient.
var/grad_color
///Does the species use skintones or not? As of now only used by humans.
var/use_skintones = FALSE
@@ -678,6 +682,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(!hair_hidden || dynamic_hair_suffix)
var/mutable_appearance/hair_overlay = mutable_appearance(layer = -HAIR_LAYER)
var/mutable_appearance/gradient_overlay = mutable_appearance(layer = -HAIR_LAYER)
if(!hair_hidden && !H.getorgan(/obj/item/organ/brain)) //Applies the debrained overlay if there is no brain
if(!(NOBLOOD in species_traits))
hair_overlay.icon = 'icons/mob/human_parts.dmi'
@@ -713,8 +718,21 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
hair_overlay.color = "#" + hair_color
else
hair_overlay.color = "#" + H.hair_color
//Gradients
grad_style = H.grad_style
grad_color = H.grad_color
if(grad_style)
var/datum/sprite_accessory/gradient = GLOB.hair_gradients_list[grad_style]
var/icon/temp = icon(gradient.icon, gradient.icon_state)
var/icon/temp_hair = icon(hair_file, hair_state)
temp.Blend(temp_hair, ICON_ADD)
gradient_overlay.icon = temp
gradient_overlay.color = "#" + grad_color
else
hair_overlay.color = forced_colour
hair_overlay.alpha = hair_alpha
if(OFFSET_HAIR in H.dna.species.offset_features)
@@ -723,6 +741,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(hair_overlay.icon)
standing += hair_overlay
standing += gradient_overlay
if(standing.len)
H.overlays_standing[HAIR_LAYER] = standing
@@ -103,13 +103,16 @@
var/_method = override[thing]
if(_method == ITEM_PARRY)
using_item = thing
tool = using_item
method = ITEM_PARRY
data = using_item.block_parry_data
else if(_method == UNARMED_PARRY)
method = UNARMED_PARRY
tool = src
data = thing
if(!using_item && !method && length(other_items))
using_item = other_items[1]
tool = using_item
method = ITEM_PARRY
data = using_item.block_parry_data
if(!method)
@@ -3,12 +3,15 @@
/**
* Returns the thing in our active hand (whatever is in our active module-slot, in this case)
*
* Arguments
* * get_gripper - If the active module is a gripper, should we return the gripper or the contained item? (if the gripper contains nothing, returns the gripper anyways)
*/
/mob/living/silicon/robot/get_active_held_item()
/mob/living/silicon/robot/get_active_held_item(get_gripper = FALSE)
var/item = module_active
// snowflake handler for the gripper
if(istype(item, /obj/item/weapon/gripper))
var/obj/item/weapon/gripper/G = item
if(istype(item, /obj/item/gripper) && !get_gripper)
var/obj/item/gripper/G = item
if(G.wrapped)
if(G.wrapped.loc != G)
G.wrapped = null
@@ -284,9 +287,14 @@
/**
* Unequips the active held item, if there is one.
*
* Will always consider dropping gripper contents first.
*/
/mob/living/silicon/robot/proc/uneq_active()
if(module_active)
var/obj/item/gripper/gripper = get_active_held_item(TRUE)
if(istype(gripper) && gripper.drop_held())
return
unequip_module_from_slot(module_active, get_selected_module())
/**
@@ -302,11 +310,12 @@
* Checks if the item is currently in a slot.
*
* If the item is found in a slot, this returns TRUE. Otherwise, it returns FALSE
* Modified to accept items inside of grippers, used for `code\modules\tgui\states\hands.dm:27`
* Arguments
* * item_module - the item being checked
*/
/mob/living/silicon/robot/proc/activated(obj/item/item_module)
if(item_module in held_items)
if(get_active_held_item() == item_module || (item_module in held_items))
return TRUE
return FALSE
@@ -328,7 +328,7 @@
/obj/item/crowbar/cyborg,
/obj/item/healthanalyzer,
/obj/item/reagent_containers/borghypo,
/obj/item/weapon/gripper/medical,
/obj/item/gripper/medical,
/obj/item/reagent_containers/dropper,
/obj/item/reagent_containers/syringe,
/obj/item/surgical_drapes,
@@ -448,7 +448,7 @@
/obj/item/analyzer,
/obj/item/storage/part_replacer/cyborg,
/obj/item/holosign_creator/combifan,
/obj/item/weapon/gripper,
/obj/item/gripper,
/obj/item/lightreplacer/cyborg,
/obj/item/geiger_counter/cyborg,
/obj/item/assembly/signaler/cyborg,
@@ -923,7 +923,7 @@
/obj/item/gun/energy/kinetic_accelerator/cyborg,
/obj/item/gun/energy/plasmacutter/cyborg,
/obj/item/gps/cyborg,
/obj/item/weapon/gripper/mining,
/obj/item/gripper/mining,
/obj/item/cyborg_clamp,
/obj/item/stack/marker_beacon,
/obj/item/destTagger,
@@ -1075,7 +1075,7 @@
/obj/item/multitool/cyborg,
/obj/item/storage/part_replacer/cyborg,
/obj/item/holosign_creator/atmos,
/obj/item/weapon/gripper,
/obj/item/gripper,
/obj/item/lightreplacer/cyborg,
/obj/item/stack/sheet/metal/cyborg,
/obj/item/stack/sheet/glass/cyborg,
@@ -147,6 +147,7 @@ GLOBAL_LIST(bad_gremlin_items)
/mob/living/simple_animal/hostile/gremlin/death(gibbed)
walk(src,0)
QDEL_NULL(access_card)
return ..()
/mob/living/simple_animal/hostile/gremlin/Life()
@@ -528,21 +528,7 @@ Difficulty: Very Hard
/obj/machinery/anomalous_crystal/emitter/ActivationReaction(mob/user, method)
if(..())
var/obj/item/projectile/P = new generated_projectile(get_turf(src))
P.setDir(dir)
switch(dir)
if(NORTH)
P.yo = 20
P.xo = 0
if(EAST)
P.yo = 0
P.xo = 20
if(WEST)
P.yo = 0
P.xo = -20
else
P.yo = -20
P.xo = 0
P.fire()
P.fire(angle2dir(dir))
/obj/machinery/anomalous_crystal/dark_reprise //Revives anyone nearby, but turns them into shadowpeople and renders them uncloneable, so the crystal is your only hope of getting up again if you go down.
observer_desc = "When activated, this crystal revives anyone nearby, but turns them into Shadowpeople and makes them unclonable, making the crystal their only hope of getting up again."
@@ -32,7 +32,7 @@ Key procs
/// Unique ID. You can never have different modifications with the same ID. By default, this SHOULD NOT be set. Only set it for cases where you're dynamically making modifiers/need to have two types overwrite each other. If unset, uses path (converted to text) as ID.
var/id
/// Higher ones override lower priorities. This is NOT used for ID, ID must be unique, if it isn't unique the newer one overwrites automatically if overriding.
/// Determines order. Lower priorities are applied first.
var/priority = 0
var/flags = NONE
@@ -66,8 +66,10 @@ Key procs
if(!complex_calculation || (multiplicative_slowdown > 0)) // we aren't limiting how much things can slowdown.. yet.
return existing + multiplicative_slowdown
var/current_tiles = 10 / max(existing, world.tick_lag)
var/minimum_speed = 10 / min(current_tiles + max_tiles_per_second_boost, max(current_tiles, absolute_max_tiles_per_second))
return max(minimum_speed, existing + multiplicative_slowdown)
// multiplicative_slowdown is negative due to our first check
var/max_buff_to = max(existing + multiplicative_slowdown, 10 / absolute_max_tiles_per_second, 10 / (current_tiles + max_tiles_per_second_boost))
// never slow the user
return min(existing, max_buff_to)
GLOBAL_LIST_EMPTY(movespeed_modification_cache)
+3 -3
View File
@@ -7,7 +7,7 @@
/datum/movespeed_modifier/reagent/ephedrine
// strong painkiller effect that caps out at slightly above runspeed
multiplicative_slowdown = -1.5
priority = -100
priority = 500
complex_calculation = TRUE
absolute_max_tiles_per_second = 7
@@ -21,14 +21,14 @@
// extremely strong painkiller effect: allows user to run at old sprint speeds but not over by cancelling out slowdowns.
// however, will not make user go faster than that
multiplicative_slowdown = -4
priority = -100
priority = 500
complex_calculation = TRUE
absolute_max_tiles_per_second = 8
/datum/movespeed_modifier/reagent/methamphetamine
// very strong painkiller effect that caps out at slightly above runspeed
multiplicative_slowdown = -2.5
priority = -100
priority = 500
complex_calculation = TRUE
absolute_max_tiles_per_second = 7.5
@@ -19,14 +19,14 @@
/datum/movespeed_modifier/status_effect/tased
multiplicative_slowdown = 1.5
priority = 50
priority = 1500
/datum/movespeed_modifier/status_effect/domain
multiplicative_slowdown = 3
/datum/movespeed_modifier/status_effect/tased/no_combat_mode
multiplicative_slowdown = 8
priority = 100
priority = 1500
/datum/movespeed_modifier/status_effect/electrostaff
multiplicative_slowdown = 1
@@ -55,7 +55,7 @@
/datum/movespeed_modifier/status_effect/slime/light_pink
// decently good painkiller + speedup effect
blacklisted_movetypes = FLYING | FLOATING
priority = -150 // someday we really need to make these defines lmao
priority = 500 // someday we really need to make these defines lmao
multiplicative_slowdown = -2
complex_calculation = TRUE
absolute_max_tiles_per_second = 7
@@ -5,12 +5,6 @@
obj_flags = CAN_BE_HIT | UNIQUE_RENAME
circuit = /obj/item/circuitboard/machine/hydroponics/automagic
/obj/machinery/hydroponics/constructable/automagic/attackby(obj/item/O, mob/user, params)
if(istype(O, /obj/item/reagent_containers))
return FALSE //avoid fucky wuckies
..()
/obj/machinery/hydroponics/constructable/automagic/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
. = ..()
if(. == SUCCESSFUL_UNFASTEN)
+46 -38
View File
@@ -62,6 +62,7 @@
var/fermiIsReacting = FALSE //that prevents multiple reactions from occurring (i.e. add_reagent calls to process_reactions(), this stops any extra reactions.)
var/fermiReactID //instance of the chem reaction used during a fermireaction, kept here so it's cache isn't lost between loops/procs.
var/value_multiplier = DEFAULT_REAGENTS_VALUE //used for cargo reagents selling.
var/force_alt_taste = FALSE
/datum/reagents/New(maximum=100, new_flags = NONE, new_value = DEFAULT_REAGENTS_VALUE)
maximum_volume = maximum
@@ -1120,47 +1121,54 @@
. = locate(type) in cached_reagents
/datum/reagents/proc/generate_taste_message(minimum_percent=15)
// the lower the minimum percent, the more sensitive the message is.
var/list/out = list()
var/list/tastes = list() //descriptor = strength
if(minimum_percent <= 100)
for(var/datum/reagent/R in reagent_list)
if(!R.taste_mult)
continue
if(istype(R, /datum/reagent/consumable/nutriment))
var/list/taste_data = R.data
for(var/taste in taste_data)
var/ratio = taste_data[taste]
var/amount = ratio * R.taste_mult * R.volume
if(taste in tastes)
tastes[taste] += amount
else
tastes[taste] = amount
else
var/taste_desc = R.taste_description
var/taste_amount = R.volume * R.taste_mult
if(taste_desc in tastes)
tastes[taste_desc] += taste_amount
else
tastes[taste_desc] = taste_amount
//deal with percentages
// TODO it would be great if we could sort these from strong to weak
var/total_taste = counterlist_sum(tastes)
if(total_taste > 0)
for(var/taste_desc in tastes)
var/percent = tastes[taste_desc]/total_taste * 100
if(percent < minimum_percent)
if(!force_alt_taste)
// the lower the minimum percent, the more sensitive the message is.
var/list/tastes = list() //descriptor = strength
if(minimum_percent <= 100)
for(var/datum/reagent/R in reagent_list)
if(!R.taste_mult)
continue
var/intensity_desc = "a hint of"
if(ISINRANGE(percent, minimum_percent * 2, minimum_percent * 3)|| percent == 100)
intensity_desc = ""
else if(percent > minimum_percent * 3)
intensity_desc = "the strong flavor of"
if(intensity_desc != "")
out += "[intensity_desc] [taste_desc]"
if(istype(R, /datum/reagent/consumable/nutriment))
var/list/taste_data = R.data
for(var/taste in taste_data)
var/ratio = taste_data[taste]
var/amount = ratio * R.taste_mult * R.volume
if(taste in tastes)
tastes[taste] += amount
else
tastes[taste] = amount
else
out += "[taste_desc]"
var/taste_desc = R.taste_description
var/taste_amount = R.volume * R.taste_mult
if(taste_desc in tastes)
tastes[taste_desc] += taste_amount
else
tastes[taste_desc] = taste_amount
//deal with percentages
// TODO it would be great if we could sort these from strong to weak
var/total_taste = counterlist_sum(tastes)
if(total_taste > 0)
for(var/taste_desc in tastes)
var/percent = tastes[taste_desc]/total_taste * 100
if(percent < minimum_percent)
continue
var/intensity_desc = "a hint of"
if(ISINRANGE(percent, minimum_percent * 2, minimum_percent * 3)|| percent == 100)
intensity_desc = ""
else if(percent > minimum_percent * 3)
intensity_desc = "the strong flavor of"
if(intensity_desc != "")
out += "[intensity_desc] [taste_desc]"
else
out += "[taste_desc]"
else
// alternate taste is to force the taste of the atom if its a food item
if(my_atom && isfood(my_atom))
var/obj/item/reagent_containers/food/snacks/F = my_atom
out = F.tastes
return english_list(out, "something indescribable")
@@ -58,6 +58,7 @@
name = "combat stimulant injector"
desc = "A modified air-needle autoinjector, used by support operatives to quickly heal injuries in combat and get people back in the fight."
amount_per_transfer_from_this = 10
item_state = "combat_hypo"
icon_state = "combat_hypo"
volume = 100
ignore_flags = 1 // So they can heal their comrades.
@@ -69,17 +70,27 @@
list_reagents = list(/datum/reagent/medicine/epinephrine = 30, /datum/reagent/medicine/omnizine = 30, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/atropine = 15)
/obj/item/reagent_containers/hypospray/combat/nanites
desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with experimental medical compounds for rapid healing."
name = "experimental combat stimulant injector"
desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with experimental medical nanites and a stimulant for rapid healing and a combat boost."
item_state = "nanite_hypo"
icon_state = "nanite_hypo"
volume = 100
list_reagents = list(/datum/reagent/medicine/adminordrazine/quantum_heal = 80, /datum/reagent/medicine/synaptizine = 20)
/obj/item/reagent_containers/hypospray/magillitis
name = "experimental autoinjector"
desc = "A modified air-needle autoinjector with a small single-use reservoir. It contains an experimental serum."
icon_state = "combat_hypo"
volume = 5
reagent_flags = NONE
list_reagents = list(/datum/reagent/magillitis = 5)
/obj/item/reagent_containers/hypospray/combat/nanites/update_icon()
if(reagents.total_volume > 0)
icon_state = initial(icon_state)
else
icon_state = "[initial(icon_state)]0"
/obj/item/reagent_containers/hypospray/combat/heresypurge
name = "holy water piercing injector"
desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with 5 doses of a holy water and pacifier mixture. Not for use on your teammates."
item_state = "holy_hypo"
icon_state = "holy_hypo"
volume = 250
list_reagents = list(/datum/reagent/water/holywater = 150, /datum/reagent/peaceborg_tire = 50, /datum/reagent/peaceborg_confuse = 50)
amount_per_transfer_from_this = 50
//MediPens
@@ -136,6 +147,8 @@
/obj/item/reagent_containers/hypospray/medipen/ekit
name = "emergency first-aid autoinjector"
desc = "An epinephrine medipen with extra coagulant and antibiotics to help stabilize bad cuts and burns."
icon_state = "firstaid"
item_state = "firstaid"
volume = 15
amount_per_transfer_from_this = 15
list_reagents = list(/datum/reagent/medicine/epinephrine = 12, /datum/reagent/medicine/coagulant = 2.5, /datum/reagent/medicine/spaceacillin = 0.5)
@@ -143,15 +156,19 @@
/obj/item/reagent_containers/hypospray/medipen/blood_loss
name = "hypovolemic-response autoinjector"
desc = "A medipen designed to stabilize and rapidly reverse severe bloodloss."
icon_state = "hypovolemic"
item_state = "hypovolemic"
volume = 15
amount_per_transfer_from_this = 15
list_reagents = list(/datum/reagent/medicine/epinephrine = 5, /datum/reagent/medicine/coagulant = 2.5, /datum/reagent/iron = 3.5, /datum/reagent/medicine/salglu_solution = 4)
/obj/item/reagent_containers/hypospray/medipen/stimulants
name = "illegal stimpack medipen"
desc = "A highly illegal medipen due to its load and small injections, allow for five uses before being drained"
name = "stimpack medipen"
desc = "Contains stimulants."
icon_state = "syndipen"
item_state = "syndipen"
volume = 50
amount_per_transfer_from_this = 10
amount_per_transfer_from_this = 50
list_reagents = list(/datum/reagent/medicine/stimulants = 50)
/obj/item/reagent_containers/hypospray/medipen/stimulants/baseball
@@ -166,6 +183,7 @@
name = "stimpack medipen"
desc = "A rapid way to stimulate your body's adrenaline, allowing for freer movement in restrictive armor."
icon_state = "stimpen"
item_state = "stimpen"
volume = 20
amount_per_transfer_from_this = 20
list_reagents = list(/datum/reagent/medicine/ephedrine = 10, /datum/reagent/consumable/coffee = 10)
@@ -177,20 +195,79 @@
/obj/item/reagent_containers/hypospray/medipen/morphine
name = "morphine medipen"
desc = "A rapid way to get you out of a tight situation and fast! You'll feel rather drowsy, though."
icon_state = "morphen"
item_state = "morphen"
volume = 10
amount_per_transfer_from_this = 10
list_reagents = list(/datum/reagent/medicine/morphine = 10)
/obj/item/reagent_containers/hypospray/medipen/penacid
name = "pentetic acid medipen"
desc = "A autoinjector containing pentetic acid, used to reduce high levels of radiations and moderate toxins."
icon_state = "penacid"
item_state = "penacid"
volume = 10
amount_per_transfer_from_this = 10
list_reagents = list(/datum/reagent/medicine/pen_acid = 10)
/obj/item/reagent_containers/hypospray/medipen/atropine
name = "atropine autoinjector"
desc = "A rapid way to save a person from a critical injury state!"
icon_state = "atropen"
item_state = "atropen"
volume = 10
amount_per_transfer_from_this = 10
list_reagents = list(/datum/reagent/medicine/atropine = 10)
/obj/item/reagent_containers/hypospray/medipen/salacid
name = "salicyclic acid medipen"
desc = "A autoinjector containing salicyclic acid, used to treat severe brute damage."
icon_state = "salacid"
item_state = "salacid"
volume = 10
amount_per_transfer_from_this = 10
list_reagents = list(/datum/reagent/medicine/sal_acid = 10)
/obj/item/reagent_containers/hypospray/medipen/oxandrolone
name = "oxandrolone medipen"
desc = "A autoinjector containing oxandrolone, used to treat severe burns."
icon_state = "oxapen"
item_state = "oxapen"
volume = 10
amount_per_transfer_from_this = 10
list_reagents = list(/datum/reagent/medicine/oxandrolone = 10)
/obj/item/reagent_containers/hypospray/medipen/salbutamol
name = "salbutamol medipen"
desc = "A autoinjector containing salbutamol, used to heal oxygen damage quickly."
icon_state = "salpen"
item_state = "salpen"
volume = 10
amount_per_transfer_from_this = 10
list_reagents = list(/datum/reagent/medicine/salbutamol = 10)
/obj/item/reagent_containers/hypospray/medipen/tuberculosiscure
name = "BVAK autoinjector"
desc = "Bio Virus Antidote Kit autoinjector. Has a two use system for yourself, and someone else. Inject when infected."
icon_state = "stimpen"
icon_state = "tbpen"
item_state = "tbpen"
volume = 60
amount_per_transfer_from_this = 30
list_reagents = list(/datum/reagent/medicine/atropine = 10, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/salbutamol = 20, /datum/reagent/medicine/spaceacillin = 20)
/obj/item/reagent_containers/hypospray/medipen/tuberculosiscure/update_icon()
if(reagents.total_volume > 30)
icon_state = initial(icon_state)
else if (reagents.total_volume > 0)
icon_state = "[initial(icon_state)]1"
else
icon_state = "[initial(icon_state)]0"
/obj/item/reagent_containers/hypospray/medipen/survival
name = "survival medipen"
desc = "A medipen for surviving in the harshest of environments, heals and protects from environmental hazards. WARNING: Do not inject more than one pen in quick succession."
icon_state = "stimpen"
icon_state = "minepen"
item_state = "minepen"
volume = 52
amount_per_transfer_from_this = 52
list_reagents = list(/datum/reagent/medicine/salbutamol = 10, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/neo_jelly = 15, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/lavaland_extract = 2)
@@ -198,16 +275,21 @@
/obj/item/reagent_containers/hypospray/medipen/firelocker
name = "fire treatment medipen"
desc = "A medipen that has been fulled with burn healing chemicals for personnel without advanced medical knowledge."
icon_state = "firepen"
item_state = "firepen"
volume = 15
amount_per_transfer_from_this = 15
list_reagents = list(/datum/reagent/medicine/oxandrolone = 5, /datum/reagent/medicine/kelotane = 10)
/obj/item/reagent_containers/hypospray/combat/heresypurge
name = "holy water autoinjector"
desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with 5 doses of a holy water mixture."
volume = 250
list_reagents = list(/datum/reagent/water/holywater = 150, /datum/reagent/peaceborg_tire = 50, /datum/reagent/peaceborg_confuse = 50)
amount_per_transfer_from_this = 50
/obj/item/reagent_containers/hypospray/medipen/magillitis
name = "experimental autoinjector"
desc = "A custom-frame needle injector with a small single-use reservoir, containing an experimental serum. Unlike the more common medipen frame, it cannot pierce through protective armor or hardsuits, nor can the chemical inside be extracted."
icon_state = "gorillapen"
item_state = "gorillapen"
volume = 5
ignore_flags = 0
reagent_flags = NONE
list_reagents = list(/datum/reagent/magillitis = 5)
#define HYPO_SPRAY 0
#define HYPO_INJECT 1
+2 -2
View File
@@ -63,10 +63,10 @@
name = "DNA Sampler"
desc = "Can be used to take chemical and genetic samples of pretty much anything."
icon = 'icons/obj/syringe.dmi'
item_state = "hypo"
item_state = "sampler"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
icon_state = "hypo"
icon_state = "sampler"
item_flags = NOBLUDGEON
var/list/animals = list()
var/list/plants = list()
+33 -1
View File
@@ -32,47 +32,79 @@
/// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)`
#define TEST_FOCUS(test_path) ##test_path { focus = TRUE; }
/// Constants indicating unit test completion status
#define UNIT_TEST_PASSED 0
#define UNIT_TEST_FAILED 1
#define UNIT_TEST_SKIPPED 2
#define TEST_DEFAULT 1
#define TEST_DEL_WORLD INFINITY
/// A trait source when adding traits through unit tests
#define TRAIT_SOURCE_UNIT_TESTS "unit_tests"
#include "anchored_mobs.dm"
#include "bespoke_id.dm"
#include "binary_insert.dm"
// #include "bloody_footprints.dm"
// #include "breath.dm"
// #include "card_mismatch.dm"
#include "chain_pull_through_space.dm"
// #include "combat.dm"
#include "component_tests.dm"
// #include "connect_loc.dm"
// #include "confusion.dm"
// #include "crayons.dm"
// #include "create_and_destroy.dm"
// #include "designs.dm"
// #include "dynamic_ruleset_sanity.dm"
// #include "egg_glands.dm"
// #include "emoting.dm"
// #include "food_edibility_check.dm"
// #include "greyscale_config.dm"
// #include "heretic_knowledge.dm"
// #include "holidays.dm"
#include "initialize_sanity.dm"
// #include "hydroponics_harvest.dm"
// #include "keybinding_init.dm"
#include "machine_disassembly.dm"
#include "medical_wounds.dm"
#include "merge_type.dm"
// #include "metabolizing.dm"
// #include "ntnetwork_tests.dm"
// #include "outfit_sanity.dm"
// #include "pills.dm"
// #include "plantgrowth_tests.dm"
// #include "projectiles.dm"
// #include "rcd.dm"
#include "reagent_id_typos.dm"
// #include "reagent_mod_expose.dm"
// #include "reagent_mod_procs.dm"
#include "reagent_recipe_collisions.dm"
#include "resist.dm"
// #include "say.dm"
// #include "security_officer_distribution.dm"
// #include "serving_tray.dm"
// #include "siunit.dm"
#include "spawn_humans.dm"
#include "spawn_mobs.dm"
// #include "species_whitelists.dm"
// #include "stomach.dm"
// #include "strippable.dm"
#include "subsystem_init.dm"
#include "surgeries.dm"
#include "teleporters.dm"
#include "tgui_create_message.dm"
#include "timer_sanity.dm"
#include "unit_test.dm"
// #include "wizard.dm"
/// CIT TESTS
#include "character_saving.dm"
#ifdef REFERENCE_TRACKING //Don't try and parse this file if ref tracking isn't turned on. IE: don't parse ref tracking please mr linter
#include "find_reference_sanity.dm"
#endif
#undef TEST_ASSERT
#undef TEST_ASSERT_EQUAL
#undef TEST_ASSERT_NOTEQUAL
@@ -1,6 +1,6 @@
/datum/unit_test/chain_pull_through_space
var/turf/open/space/space_tile
var/turf/claimed_tile
var/claimed_tile
var/mob/living/carbon/human/alice
var/mob/living/carbon/human/bob
var/mob/living/carbon/human/charlie
@@ -9,25 +9,25 @@
..()
// Create a space tile that goes to another z-level
claimed_tile = run_loc_bottom_left
claimed_tile = run_loc_floor_bottom_left.type
space_tile = new(locate(run_loc_bottom_left.x, run_loc_bottom_left.y, run_loc_bottom_left.z))
space_tile = new(locate(run_loc_floor_bottom_left.x, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
space_tile.destination_x = 100
space_tile.destination_y = 100
space_tile.destination_z = 5
// Create our list of humans, all adjacent to one another
alice = new(locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z))
alice = new(locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
alice.name = "Alice"
bob = new(locate(run_loc_bottom_left.x + 3, run_loc_bottom_left.y, run_loc_bottom_left.z))
bob = new(locate(run_loc_floor_bottom_left.x + 3, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
bob.name = "Bob"
charlie = new(locate(run_loc_bottom_left.x + 4, run_loc_bottom_left.y, run_loc_bottom_left.z))
charlie = new(locate(run_loc_floor_bottom_left.x + 4, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
charlie.name = "Charlie"
/datum/unit_test/chain_pull_through_space/Destroy()
space_tile.copyTurf(claimed_tile)
space_tile.ChangeTurf(claimed_tile)
qdel(alice)
qdel(bob)
qdel(charlie)
@@ -40,14 +40,14 @@
bob.start_pulling(charlie)
// Walk normally to the left, make sure we're still a chain
alice.Move(locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z))
if (bob.x != run_loc_bottom_left.x + 2)
alice.Move(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
if (bob.x != run_loc_floor_bottom_left.x + 2)
return Fail("During normal move, Bob was not at the correct x ([bob.x])")
if (charlie.x != run_loc_bottom_left.x + 3)
if (charlie.x != run_loc_floor_bottom_left.x + 3)
return Fail("During normal move, Charlie was not at the correct x ([charlie.x])")
// We're going through the space turf now that should teleport us
alice.Move(run_loc_bottom_left)
alice.Move(run_loc_floor_bottom_left)
if (alice.z != space_tile.destination_z)
return Fail("Alice did not teleport to the destination z-level. Current location: ([alice.x], [alice.y], [alice.z])")
@@ -0,0 +1,111 @@
///Used to test the completeness of the reference finder proc.
/datum/unit_test/find_reference_sanity
/atom/movable/ref_holder
var/atom/movable/ref_test/test
var/list/test_list = list()
var/list/test_assoc_list = list()
/atom/movable/ref_holder/Destroy()
test = null
test_list.Cut()
test_assoc_list.Cut()
return ..()
/atom/movable/ref_test
var/atom/movable/ref_test/self_ref
/atom/movable/ref_test/Destroy(force)
self_ref = null
return ..()
/datum/unit_test/find_reference_sanity/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Sanity check
victim.DoSearchVar(testbed, "Sanity Check", search_time = 1) //We increment search time to get around an optimization
TEST_ASSERT(!victim.found_refs.len, "The ref-tracking tool found a ref where none existed")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_baseline/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Set up for the first round of tests
testbed.test = victim
testbed.test_list += victim
testbed.test_assoc_list["baseline"] = victim
victim.DoSearchVar(testbed, "First Run", search_time = 2)
TEST_ASSERT(victim.found_refs["test"], "The ref-tracking tool failed to find a regular value")
TEST_ASSERT(victim.found_refs[testbed.test_list], "The ref-tracking tool failed to find a list entry")
TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list value")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_exotic/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Second round, bit harder this time
testbed.overlays += victim
testbed.vis_contents += victim
testbed.test_assoc_list[victim] = TRUE
victim.DoSearchVar(testbed, "Second Run", search_time = 3)
//This is another sanity check
TEST_ASSERT(!victim.found_refs[testbed.overlays], "The ref-tracking tool found an overlays entry? That shouldn't be possible")
TEST_ASSERT(victim.found_refs[testbed.vis_contents], "The ref-tracking tool failed to find a vis_contents entry")
TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list key")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_esoteric/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Let's get a bit esoteric
victim.self_ref = victim
var/list/to_find = list(victim)
testbed.test_list += list(to_find)
var/list/to_find_assoc = list(victim)
testbed.test_assoc_list["Nesting"] = to_find_assoc
victim.DoSearchVar(victim, "Third Run Self", search_time = 4)
victim.DoSearchVar(testbed, "Third Run Testbed", search_time = 4)
TEST_ASSERT(victim.found_refs["self_ref"], "The ref-tracking tool failed to find a self reference")
TEST_ASSERT(victim.found_refs[to_find], "The ref-tracking tool failed to find a nested list entry")
TEST_ASSERT(victim.found_refs[to_find_assoc], "The ref-tracking tool failed to find a nested assoc list entry")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_null_key_entry/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Calm before the storm
testbed.test_assoc_list = list(null = victim)
victim.DoSearchVar(testbed, "Fourth Run", search_time = 5)
TEST_ASSERT(testbed.test_assoc_list, "The ref-tracking tool failed to find a null key'd assoc list entry")
/datum/unit_test/find_reference_assoc_investigation/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
SSgarbage.should_save_refs = TRUE
//Let's do some more complex assoc list investigation
var/list/to_find_in_key = list(victim)
testbed.test_assoc_list[to_find_in_key] = list("memes")
var/list/to_find_null_assoc_nested = list(victim)
testbed.test_assoc_list[null] = to_find_null_assoc_nested
victim.DoSearchVar(testbed, "Fifth Run", search_time = 6)
TEST_ASSERT(victim.found_refs[to_find_in_key], "The ref-tracking tool failed to find a nested assoc list key")
TEST_ASSERT(victim.found_refs[to_find_null_assoc_nested], "The ref-tracking tool failed to find a null key'd nested assoc list entry")
SSgarbage.should_save_refs = FALSE
@@ -1,11 +0,0 @@
/datum/unit_test/initialize_sanity/Run()
if(length(SSatoms.BadInitializeCalls))
Fail("Bad Initialize() calls detected. Please read logs.")
var/list/init_failures_to_text = list(
"[BAD_INIT_QDEL_BEFORE]" = "Qdeleted Before Initialized",
"[BAD_INIT_DIDNT_INIT]" = "Did Not Initialize",
"[BAD_INIT_SLEPT]" = "Initialize() Slept",
"[BAD_INIT_NO_HINT]" = "No Initialize() Hint Returned",
)
for(var/failure in SSatoms.BadInitializeCalls)
log_world("[failure]: [init_failures_to_text["[SSatoms.BadInitializeCalls[failure]]"]]") // You like stacked brackets?
-23
View File
@@ -1,23 +0,0 @@
/// Test to verify message mods are parsed correctly
/datum/unit_test/get_message_mods
var/mob/host_mob
/datum/unit_test/get_message_mods/Run()
host_mob = allocate(/mob/living/carbon/human)
test("Hello", "Hello", list())
test(";HELP", "HELP", list(MODE_HEADSET = TRUE))
test(";%Never gonna give you up", "Never gonna give you up", list(MODE_HEADSET = TRUE, MODE_SING = TRUE))
test(".s Gun plz", "Gun plz", list(RADIO_KEY = RADIO_KEY_SECURITY, RADIO_EXTENSION = RADIO_CHANNEL_SECURITY))
test("...What", "...What", list())
/datum/unit_test/get_message_mods/proc/test(message, expected_message, list/expected_mods)
var/list/mods = list()
TEST_ASSERT_EQUAL(host_mob.get_message_mods(message, mods), expected_message, "Chopped message was not what we expected. Message: [message]")
for (var/mod_key in mods)
TEST_ASSERT_EQUAL(mods[mod_key], expected_mods[mod_key], "The value for [mod_key] was not what we expected. Message: [message]")
expected_mods -= mod_key
if (expected_mods.len)
Fail("Some message mods were expected, but were not returned by get_message_mods: [json_encode(expected_mods)]. Message: [message]")
+1 -1
View File
@@ -1,5 +1,5 @@
/datum/unit_test/spawn_humans/Run()
var/locs = block(run_loc_bottom_left, run_loc_top_right)
var/locs = block(run_loc_floor_bottom_left, run_loc_floor_top_right)
for(var/I in 1 to 5)
new /mob/living/carbon/human(pick(locs))
+8
View File
@@ -0,0 +1,8 @@
///Unit test that spawns all mobs that can be spawned by golden slimes
/datum/unit_test/spawn_mobs
/datum/unit_test/spawn_mobs/Run()
for(var/_animal in typesof(/mob/living/simple_animal))
var/mob/living/simple_animal/animal = _animal
if (initial(animal.gold_core_spawnable) == HOSTILE_SPAWN || initial(animal.gold_core_spawnable) == FRIENDLY_SPAWN)
allocate(_animal)
+2 -2
View File
@@ -1,8 +1,8 @@
/datum/unit_test/auto_teleporter_linking/Run()
// Put down the teleporter machinery
var/obj/machinery/teleport/hub/hub = allocate(/obj/machinery/teleport/hub)
var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z))
var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z))
var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
TEST_ASSERT_EQUAL(hub.power_station, station, "Hub didn't link to the station")
TEST_ASSERT_EQUAL(station.teleporter_console, computer, "Station didn't link to the teleporter console")
@@ -0,0 +1,28 @@
/// Test that `TGUI_CREATE_MESSAGE` is correctly implemented
/datum/unit_test/tgui_create_message
/datum/unit_test/tgui_create_message/Run()
var/type = "something/here"
var/list/payload = list(
"name" = "Terry McTider",
"heads_caved" = 100,
"accomplishments" = list(
"nothing",
"literally nothing",
list(
"something" = "just kidding",
),
),
)
var/message = TGUI_CREATE_MESSAGE(type, payload)
// Ensure consistent output to compare by performing a round-trip.
var/output = json_encode(json_decode(url_decode(message)))
var/expected = json_encode(list(
"type" = type,
"payload" = payload,
))
TEST_ASSERT_EQUAL(expected, output, "TGUI_CREATE_MESSAGE didn't round trip properly")
+68 -41
View File
@@ -7,7 +7,7 @@ Call Fail() to fail the test (You should specify a reason)
You may use /New() and /Destroy() for setup/teardown respectively
You can use the run_loc_bottom_left and run_loc_top_right to get turfs for testing
You can use the run_loc_floor_bottom_left and run_loc_floor_top_right to get turfs for testing
*/
@@ -19,39 +19,46 @@ GLOBAL_VAR(test_log)
//Bit of metadata for the future maybe
var/list/procs_tested
/// The bottom left turf of the testing zone
var/turf/run_loc_bottom_left
/// The top right turf of the testing zone
var/turf/run_loc_top_right
/// The type of turf to allocate for the testing zone
var/test_turf_type = /turf/open/floor/plasteel
/// The bottom left floor turf of the testing zone
var/turf/run_loc_floor_bottom_left
/// The top right floor turf of the testing zone
var/turf/run_loc_floor_top_right
///The priority of the test, the larger it is the later it fires
var/priority = TEST_DEFAULT
//internal shit
var/focus = FALSE
var/succeeded = TRUE
var/list/allocated
var/list/fail_reasons
var/static/datum/turf_reservation/turf_reservation
var/static/datum/turf_reservation/reservation
/proc/cmp_unit_test_priority(datum/unit_test/a, datum/unit_test/b)
return initial(a.priority) - initial(b.priority)
/datum/unit_test/New()
if (isnull(turf_reservation))
turf_reservation = SSmapping.RequestBlockReservation(5, 5)
if (isnull(reservation))
reservation = SSmapping.RequestBlockReservation(5, 5)
for (var/turf/reserved_turf in turf_reservation.reserved_turfs)
reserved_turf.ChangeTurf(test_turf_type)
for (var/turf/reserved_turf in reservation.reserved_turfs)
reserved_turf.ChangeTurf(/turf/open/floor/plasteel)
allocated = new
run_loc_bottom_left = locate(turf_reservation.bottom_left_coords[1], turf_reservation.bottom_left_coords[2], turf_reservation.bottom_left_coords[3])
run_loc_top_right = locate(turf_reservation.top_right_coords[1], turf_reservation.top_right_coords[2], turf_reservation.top_right_coords[3])
run_loc_floor_bottom_left = locate(reservation.bottom_left_coords[1], reservation.bottom_left_coords[2], reservation.bottom_left_coords[3])
run_loc_floor_top_right = locate(reservation.top_right_coords[1], reservation.top_right_coords[2], reservation.top_right_coords[3])
TEST_ASSERT(isfloorturf(run_loc_floor_bottom_left), "run_loc_floor_bottom_left was not a floor ([run_loc_floor_bottom_left])")
TEST_ASSERT(isfloorturf(run_loc_floor_top_right), "run_loc_floor_top_right was not a floor ([run_loc_floor_top_right])")
/datum/unit_test/Destroy()
//clear the test area
for(var/atom/movable/AM in block(run_loc_bottom_left, run_loc_top_right))
qdel(AM)
QDEL_LIST(allocated)
// clear the test area
for (var/turf/turf in block(locate(1, 1, run_loc_floor_bottom_left.z), locate(world.maxx, world.maxy, run_loc_floor_bottom_left.z)))
for (var/content in turf.contents)
if (istype(content, /obj/effect/landmark))
continue
qdel(content)
return ..()
/datum/unit_test/proc/Run()
@@ -70,44 +77,64 @@ GLOBAL_VAR(test_log)
/datum/unit_test/proc/allocate(type, ...)
var/list/arguments = args.Copy(2)
if (!arguments.len)
arguments = list(run_loc_bottom_left)
arguments = list(run_loc_floor_bottom_left)
else if (arguments[1] == null)
arguments[1] = run_loc_bottom_left
arguments[1] = run_loc_floor_bottom_left
var/instance = new type(arglist(arguments))
allocated += instance
return instance
/proc/RunUnitTest(test_path, list/test_results)
var/datum/unit_test/test = new test_path
GLOB.current_test = test
var/duration = REALTIMEOFDAY
test.Run()
duration = REALTIMEOFDAY - duration
GLOB.current_test = null
GLOB.failed_any_test |= !test.succeeded
var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [test_path] [duration / 10]s")
var/list/fail_reasons = test.fail_reasons
for(var/J in 1 to LAZYLEN(fail_reasons))
log_entry += "\tREASON #[J]: [fail_reasons[J]]"
var/message = log_entry.Join("\n")
log_test(message)
test_results[test_path] = list("status" = test.succeeded ? UNIT_TEST_PASSED : UNIT_TEST_FAILED, "message" = message, "name" = test_path)
qdel(test)
/proc/RunUnitTests()
CHECK_TICK
var/tests_to_run = subtypesof(/datum/unit_test)
var/list/tests_to_run = subtypesof(/datum/unit_test)
for (var/_test_to_run in tests_to_run)
var/datum/unit_test/test_to_run = _test_to_run
if (initial(test_to_run.focus))
tests_to_run = list(test_to_run)
break
for(var/I in tests_to_run)
var/datum/unit_test/test = new I
tests_to_run = sortTim(tests_to_run, /proc/cmp_unit_test_priority)
GLOB.current_test = test
var/duration = REALTIMEOFDAY
var/list/test_results = list()
test.Run()
for(var/unit_path in tests_to_run)
CHECK_TICK //We check tick first because the unit test we run last may be so expensive that checking tick will lock up this loop forever
RunUnitTest(unit_path, test_results)
duration = REALTIMEOFDAY - duration
GLOB.current_test = null
GLOB.failed_any_test |= !test.succeeded
var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [I] [duration / 10]s")
var/list/fail_reasons = test.fail_reasons
qdel(test)
for(var/J in 1 to LAZYLEN(fail_reasons))
log_entry += "\tREASON #[J]: [fail_reasons[J]]"
log_test(log_entry.Join("\n"))
CHECK_TICK
var/file_name = "data/unit_tests.json"
fdel(file_name)
file(file_name) << json_encode(test_results)
SSticker.force_ending = TRUE
//We have to call this manually because del_text can preceed us, and SSticker doesn't fire in the post game
SSticker.ready_for_reboot = TRUE
SSticker.standard_reboot()
// /datum/map_template/unit_tests
// name = "Unit Tests Zone"
// mappath = "_maps/templates/unit_tests.dmm"
@@ -213,7 +213,7 @@
name = "Stimpack"
desc = "Stimpacks, the tool of many great heroes. Makes you nearly immune to non-lethal weaponry for about \
5 minutes after injection."
item = /obj/item/reagent_containers/syringe/stimulants
item = /obj/item/reagent_containers/hypospray/medipen/stimulants
cost = 5
surplus = 90
@@ -195,7 +195,7 @@
name = "Magillitis Serum Autoinjector"
desc = "A single-use autoinjector which contains an experimental serum that causes rapid muscular growth in Hominidae. \
Side-affects may include hypertrichosis, violent outbursts, and an unending affinity for bananas."
item = /obj/item/reagent_containers/hypospray/magillitis
item = /obj/item/reagent_containers/hypospray/medipen/magillitis
cost = 8
restricted_roles = list("Geneticist", "Chief Medical Officer")
+1
View File
@@ -9,6 +9,7 @@
/obj/item/toy/cards/deck/cas/black = 3,
/obj/item/toy/cards/deck/unum = 3,
/obj/item/cardpack/series_one = 10,
/obj/item/dyespray=3,
/obj/item/tcgcard_binder = 5)
contraband = list(/obj/item/dice/fudge = 9)
premium = list(/obj/item/melee/skateboard/pro = 3,