config&copypaste chang.

This commit is contained in:
Letter N
2020-08-20 16:36:24 +08:00
parent 3b7bca28b3
commit 2dd3d71b19
15 changed files with 614 additions and 332 deletions

View File

@@ -1,7 +1,6 @@
//config files //config files
#define CONFIG_GET(X) global.config.Get(/datum/config_entry/##X) #define CONFIG_GET(X) global.config.Get(/datum/config_entry/##X)
#define CONFIG_SET(X, Y) global.config.Set(/datum/config_entry/##X, ##Y) #define CONFIG_SET(X, Y) global.config.Set(/datum/config_entry/##X, ##Y)
#define CONFIG_GET_ENTRY(X) global.config.GetEntryDatum(/datum/config_entry/##X)
#define CONFIG_MAPS_FILE "maps.txt" #define CONFIG_MAPS_FILE "maps.txt"

View File

@@ -14,13 +14,15 @@
var/list/modes // allowed modes var/list/modes // allowed modes
var/list/gamemode_cache var/list/gamemode_cache
var/list/votable_modes // votable modes var/list/votable_modes // votable modes
// var/list/ic_filter_regex
var/list/storyteller_cache var/list/storyteller_cache
var/list/mode_names var/list/mode_names
var/list/mode_reports var/list/mode_reports
var/list/mode_false_report_weight var/list/mode_false_report_weight
var/motd var/motd
// var/policy
// var/static/regex/ic_filter_regex
/datum/controller/configuration/proc/admin_reload() /datum/controller/configuration/proc/admin_reload()
if(IsAdminAdvancedProcCall()) if(IsAdminAdvancedProcCall())
@@ -50,6 +52,11 @@
break break
loadmaplist(CONFIG_MAPS_FILE) loadmaplist(CONFIG_MAPS_FILE)
LoadMOTD() LoadMOTD()
// LoadPolicy()
// LoadChatFilter()
if (Master)
Master.OnConfigLoad()
/datum/controller/configuration/proc/full_wipe() /datum/controller/configuration/proc/full_wipe()
if(IsAdminAdvancedProcCall()) if(IsAdminAdvancedProcCall())
@@ -105,9 +112,7 @@
var/list/lines = world.file2list("[directory]/[filename]") var/list/lines = world.file2list("[directory]/[filename]")
var/list/_entries = entries var/list/_entries = entries
var/list/postload_required = list() var/list/postload_required = list()
var/linenumber = 0
for(var/L in lines) for(var/L in lines)
linenumber++
L = trim(L) L = trim(L)
if(!L) if(!L)
continue continue
@@ -135,7 +140,7 @@
if(entry == "$include") if(entry == "$include")
if(!value) if(!value)
log_config("LINE [linenumber]: Invalid $include directive: [value]") log_config("Warning: Invalid $include directive: [value]")
else else
LoadEntries(value, stack) LoadEntries(value, stack)
++. ++.
@@ -143,7 +148,7 @@
var/datum/config_entry/E = _entries[entry] var/datum/config_entry/E = _entries[entry]
if(!E) if(!E)
log_config("LINE [linenumber]: Unknown setting: '[entry]'") log_config("Unknown setting in configuration: '[entry]'")
continue continue
if(lockthis) if(lockthis)
@@ -153,9 +158,9 @@
var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by] var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by]
var/new_value = E.DeprecationUpdate(value) var/new_value = E.DeprecationUpdate(value)
var/good_update = istext(new_value) var/good_update = istext(new_value)
log_config("LINE [linenumber]: [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]") log_config("Entry [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]")
if(!warned_deprecated_configs) if(!warned_deprecated_configs)
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, "This server is using deprecated configuration settings. Please check the logs and update accordingly."), 0) DelayedMessageAdmins("This server is using deprecated configuration settings. Please check the logs and update accordingly.")
warned_deprecated_configs = TRUE warned_deprecated_configs = TRUE
if(good_update) if(good_update)
value = new_value value = new_value
@@ -163,12 +168,12 @@
else else
warning("[new_ver.type] is deprecated but gave no proper return for DeprecationUpdate()") warning("[new_ver.type] is deprecated but gave no proper return for DeprecationUpdate()")
var/validated = E.ValidateAndSet(value, TRUE) var/validated = E.ValidateAndSet(value)
if(!validated) if(!validated)
log_config("LINE [linenumber]: Failed to validate setting \"[value]\" for [entry]") log_config("Failed to validate setting \"[value]\" for [entry]")
else else
if(E.modified && !E.dupes_allowed) if(E.modified && !E.dupes_allowed)
log_config("LINE [linenumber]: Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.") log_config("Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.")
if(E.postload_required) if(E.postload_required)
postload_required[E] = TRUE postload_required[E] = TRUE
@@ -196,13 +201,6 @@
stat("[name]:", statclick) stat("[name]:", statclick)
/datum/controller/configuration/proc/Get(entry_type) /datum/controller/configuration/proc/Get(entry_type)
var/datum/config_entry/E = GetEntryDatum(entry_type)
if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]")
return
return E.config_entry_value
/datum/controller/configuration/proc/GetEntryDatum(entry_type)
var/datum/config_entry/E = entry_type var/datum/config_entry/E = entry_type
var/entry_is_abstract = initial(E.abstract_type) == entry_type var/entry_is_abstract = initial(E.abstract_type) == entry_type
if(entry_is_abstract) if(entry_is_abstract)
@@ -210,7 +208,10 @@
E = entries_by_type[entry_type] E = entries_by_type[entry_type]
if(!E) if(!E)
CRASH("Missing config entry for [entry_type]!") CRASH("Missing config entry for [entry_type]!")
return E if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]")
return
return E.config_entry_value
/datum/controller/configuration/proc/Set(entry_type, new_val) /datum/controller/configuration/proc/Set(entry_type, new_val)
var/datum/config_entry/E = entry_type var/datum/config_entry/E = entry_type
@@ -236,7 +237,6 @@
for(var/T in gamemode_cache) for(var/T in gamemode_cache)
// I wish I didn't have to instance the game modes in order to look up // I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of). // their information, but it is the only way (at least that I know of).
// for future reference: just use initial() lol
var/datum/game_mode/M = new T() var/datum/game_mode/M = new T()
if(M.config_tag) if(M.config_tag)
@@ -258,7 +258,37 @@
var/tm_info = GLOB.revdata.GetTestMergeInfo() var/tm_info = GLOB.revdata.GetTestMergeInfo()
if(motd || tm_info) if(motd || tm_info)
motd = motd ? "[motd]<br>[tm_info]" : tm_info motd = motd ? "[motd]<br>[tm_info]" : tm_info
/*
Policy file should be a json file with a single object.
Value is raw html.
Possible keywords :
Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker
Mob types : /mob/living/simple_animal/hostile/carp
Antagonist types : /datum/antagonist/highlander
Species types : /datum/species/lizard
special keywords defined in _DEFINES/admin.dm
Example config:
{
"Assistant" : "Don't kill everyone",
"/datum/antagonist/highlander" : "<b>Kill everyone</b>",
"Ash Walker" : "Kill all spacemans"
}
*/
/*
/datum/controller/configuration/proc/LoadPolicy()
policy = list()
var/rawpolicy = file2text("[directory]/policy.json")
if(rawpolicy)
var/parsed = safe_json_decode(rawpolicy)
if(!parsed)
log_config("JSON parsing failure for policy.json")
DelayedMessageAdmins("JSON parsing failure for policy.json")
else
policy = parsed
*/
/datum/controller/configuration/proc/loadmaplist(filename) /datum/controller/configuration/proc/loadmaplist(filename)
log_config("Loading config file [filename]...") log_config("Loading config file [filename]...")
filename = "[directory]/[filename]" filename = "[directory]/[filename]"
@@ -305,6 +335,8 @@
currentmap.voteweight = text2num(data) currentmap.voteweight = text2num(data)
if ("default","defaultmap") if ("default","defaultmap")
defaultmap = currentmap defaultmap = currentmap
//if ("votable")
// currentmap.votable = TRUE
if ("endmap") if ("endmap")
LAZYINITLIST(maplist) LAZYINITLIST(maplist)
maplist[currentmap.map_name] = currentmap maplist[currentmap.map_name] = currentmap
@@ -326,6 +358,7 @@
return new T return new T
return new /datum/game_mode/extended() return new /datum/game_mode/extended()
/// For dynamic.
/datum/controller/configuration/proc/pick_storyteller(storyteller_name) /datum/controller/configuration/proc/pick_storyteller(storyteller_name)
for(var/T in storyteller_cache) for(var/T in storyteller_cache)
var/datum/dynamic_storyteller/S = T var/datum/dynamic_storyteller/S = T
@@ -334,6 +367,32 @@
return T return T
return /datum/dynamic_storyteller/classic return /datum/dynamic_storyteller/classic
/// Same with this
/datum/controller/configuration/proc/get_runnable_storytellers()
var/list/datum/dynamic_storyteller/runnable_storytellers = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/storyteller_weight)
var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
var/list/min_player_counts = Get(/datum/config_entry/keyed_list/storyteller_min_players)
for(var/T in storyteller_cache)
var/datum/dynamic_storyteller/S = T
var/config_tag = initial(S.config_tag)
var/probability = (config_tag in probabilities) ? probabilities[config_tag] : initial(S.weight)
var/min_players = (config_tag in min_player_counts) ? min_player_counts[config_tag] : initial(S.min_players)
if(probability <= 0)
continue
if(length(GLOB.player_list) < min_players)
continue
if(SSpersistence.saved_storytellers.len == repeated_mode_adjust.len)
var/name = initial(S.name)
var/recent_round = min(SSpersistence.saved_storytellers.Find(name),3)
var/adjustment = 0
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(name,recent_round+1,0)
probability *= ((100-adjustment)/100)
runnable_storytellers[S] = probability
return runnable_storytellers
/datum/controller/configuration/proc/get_runnable_modes() /datum/controller/configuration/proc/get_runnable_modes()
var/list/datum/game_mode/runnable_modes = new var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
@@ -367,32 +426,6 @@
runnable_modes[M] = final_weight runnable_modes[M] = final_weight
return runnable_modes return runnable_modes
/datum/controller/configuration/proc/get_runnable_storytellers()
var/list/datum/dynamic_storyteller/runnable_storytellers = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/storyteller_weight)
var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
var/list/min_player_counts = Get(/datum/config_entry/keyed_list/storyteller_min_players)
for(var/T in storyteller_cache)
var/datum/dynamic_storyteller/S = T
var/config_tag = initial(S.config_tag)
var/probability = (config_tag in probabilities) ? probabilities[config_tag] : initial(S.weight)
var/min_players = (config_tag in min_player_counts) ? min_player_counts[config_tag] : initial(S.min_players)
if(probability <= 0)
continue
if(length(GLOB.player_list) < min_players)
continue
if(SSpersistence.saved_storytellers.len == repeated_mode_adjust.len)
var/name = initial(S.name)
var/recent_round = min(SSpersistence.saved_storytellers.Find(name),3)
var/adjustment = 0
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(name,recent_round+1,0)
probability *= ((100-adjustment)/100)
runnable_storytellers[S] = probability
return runnable_storytellers
/datum/controller/configuration/proc/get_runnable_midround_modes(crew) /datum/controller/configuration/proc/get_runnable_midround_modes(crew)
var/list/datum/game_mode/runnable_modes = new var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
@@ -418,7 +451,6 @@
/* /*
/datum/controller/configuration/proc/LoadChatFilter() /datum/controller/configuration/proc/LoadChatFilter()
var/list/in_character_filter = list() var/list/in_character_filter = list()
if(!fexists("[directory]/in_character_filter.txt")) if(!fexists("[directory]/in_character_filter.txt"))
return return
log_config("Loading config file in_character_filter.txt...") log_config("Loading config file in_character_filter.txt...")
@@ -428,8 +460,8 @@
if(findtextEx(line,"#",1,2)) if(findtextEx(line,"#",1,2))
continue continue
in_character_filter += REGEX_QUOTE(line) in_character_filter += REGEX_QUOTE(line)
ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null
syncChatRegexes()
*/ */
//Message admins when you can.
/datum/controller/configuration/proc/DelayedMessageAdmins(text)
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0)

View File

@@ -16,4 +16,4 @@
/datum/controller/proc/Recover() /datum/controller/proc/Recover()
/datum/controller/proc/stat_entry() /datum/controller/proc/stat_entry()

View File

@@ -76,7 +76,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Highlander-style: there can only be one! Kill off the old and replace it with the new. // Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(!random_seed) if(!random_seed)
random_seed = (TEST_RUN_PARAMETER in world.params) ? 29051994 : rand(1, 1e9) #ifdef UNIT_TESTS
random_seed = 29051994
#else
random_seed = rand(1, 1e9)
#endif
rand_seed(random_seed) rand_seed(random_seed)
var/list/_subsystems = list() var/list/_subsystems = list()
@@ -184,9 +188,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(delay) if(delay)
sleep(delay) sleep(delay)
if(tgs_prime)
world.TgsInitializationComplete()
if(init_sss) if(init_sss)
init_subtypes(/datum/controller/subsystem, subsystems) init_subtypes(/datum/controller/subsystem, subsystems)
@@ -219,6 +220,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
world.fps = CONFIG_GET(number/fps) world.fps = CONFIG_GET(number/fps)
var/initialized_tod = REALTIMEOFDAY var/initialized_tod = REALTIMEOFDAY
if(tgs_prime)
world.TgsInitializationComplete()
if(sleep_offline_after_initializations) if(sleep_offline_after_initializations)
world.sleep_offline = TRUE world.sleep_offline = TRUE
sleep(1) sleep(1)
@@ -643,3 +647,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
processing = CONFIG_GET(number/mc_tick_rate/base_mc_tick_rate) processing = CONFIG_GET(number/mc_tick_rate/base_mc_tick_rate)
else if (client_count > CONFIG_GET(number/mc_tick_rate/high_pop_mc_mode_amount)) else if (client_count > CONFIG_GET(number/mc_tick_rate/high_pop_mc_mode_amount))
processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate) processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate)
/datum/controller/master/proc/OnConfigLoad()
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
SS.OnConfigLoad()

View File

@@ -1,39 +1,91 @@
/**
* # Subsystem base class
*
* Defines a subsystem to be managed by the [Master Controller][/datum/controller/master]
*
* Simply define a child of this subsystem, using the [SUBSYSTEM_DEF] macro, and the MC will handle registration.
* Changing the name is required
**/
/datum/controller/subsystem /datum/controller/subsystem
// Metadata; you should define these. // Metadata; you should define these.
name = "fire coderbus" //name of the subsystem
var/init_order = INIT_ORDER_DEFAULT //order of initialization. Higher numbers are initialized first, lower numbers later. Use defines in __DEFINES/subsystems.dm for easy understanding of order.
var/wait = 20 //time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
var/priority = FIRE_PRIORITY_DEFAULT //When mutiple subsystems need to run in the same tick, higher priority subsystems will run first and be given a higher share of the tick before MC_TICK_CHECK triggers a sleep
var/flags = 0 //see MC.dm in __DEFINES Most flags must be set on world start to take full effect. (You can also restart the mc to force them to process again) /// Name of the subsystem - you must change this
name = "fire coderbus"
var/initialized = FALSE //set to TRUE after it has been initialized, will obviously never be set if the subsystem doesn't initialize /// Order of initialization. Higher numbers are initialized first, lower numbers later. Use or create defines such as [INIT_ORDER_DEFAULT] so we can see the order in one file.
var/init_order = INIT_ORDER_DEFAULT
//set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later /// Time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
// use the SS_NO_FIRE flag instead for systems that never fire to keep it from even being added to the list var/wait = 20
/// Priority Weight: When mutiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems
var/priority = FIRE_PRIORITY_DEFAULT
/// [Subsystem Flags][SS_NO_INIT] to control binary behavior. Flags must be set at compile time or before preinit finishes to take full effect. (You can also restart the mc to force them to process again)
var/flags = 0
/// This var is set to TRUE after the subsystem has been initialized.
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
var/can_fire = TRUE var/can_fire = TRUE
// Bookkeeping variables; probably shouldn't mess with these. ///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
var/last_fire = 0 //last world.time we called fire()
var/next_fire = 0 //scheduled world.time for next fire()
var/cost = 0 //average time to execute
var/tick_usage = 0 //average tick usage
var/tick_overrun = 0 //average tick overrun
var/state = SS_IDLE //tracks the current state of the ss, running, paused, etc.
var/paused_ticks = 0 //ticks this ss is taking to run right now.
var/paused_tick_usage //total tick_usage of all of our runs while pausing this run
var/ticks = 1 //how many ticks does this ss take to run on avg.
var/times_fired = 0 //number of times we have called fire()
var/queued_time = 0 //time we entered the queue, (for timing and priority reasons)
var/queued_priority //we keep a running total to make the math easier, if priority changes mid-fire that would break our running total, so we store it here
//linked list stuff for the queue
var/datum/controller/subsystem/queue_next
var/datum/controller/subsystem/queue_prev
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
var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out! /*
* The following variables are managed by the MC and should not be modified directly.
*/
/// Last world.time the subsystem completed a run (as in wasn't paused by [MC_TICK_CHECK])
var/last_fire = 0
/// Scheduled world.time for next fire()
var/next_fire = 0
/// Running average of the amount of milliseconds it takes the subsystem to complete a run (including all resumes but not the time spent paused)
var/cost = 0
/// Running average of the amount of tick usage in percents of a tick it takes the subsystem to complete a run
var/tick_usage = 0
/// Running average of the amount of tick usage (in percents of a game tick) the subsystem has spent past its allocated time without pausing
var/tick_overrun = 0
/// Tracks the current execution state of the subsystem. Used to handle subsystems that sleep in fire so the mc doesn't run them again while they are sleeping
var/state = SS_IDLE
/// Tracks how many fires the subsystem has consecutively paused on in the current run
var/paused_ticks = 0
/// Tracks how much of a tick the subsystem has consumed in the current run
var/paused_tick_usage
/// Tracks how many fires the subsystem takes to complete a run on average.
var/ticks = 1
/// Tracks the amount of completed runs for the subsystem
var/times_fired = 0
/// Time the subsystem entered the queue, (for timing and priority reasons)
var/queued_time = 0
/// Priority at the time the subsystem entered the queue. Needed to avoid changes in priority (by admins and the like) from breaking things.
var/queued_priority
/// How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out!
var/static/list/failure_strikes
/// Next subsystem in the queue of subsystems to run this tick
var/datum/controller/subsystem/queue_next
/// Previous subsystem in the queue of subsystems to run this tick
var/datum/controller/subsystem/queue_prev
//Do not blindly add vars here to the bottom, put it where it goes above
//If your var only has two values, put it in as a flag.
//Do not override //Do not override
///datum/controller/subsystem/New() ///datum/controller/subsystem/New()
@@ -46,6 +98,7 @@
//This is used so the mc knows when the subsystem sleeps. do not override. //This is used so the mc knows when the subsystem sleeps. do not override.
/datum/controller/subsystem/proc/ignite(resumed = 0) /datum/controller/subsystem/proc/ignite(resumed = 0)
SHOULD_NOT_OVERRIDE(TRUE)
set waitfor = 0 set waitfor = 0
. = SS_SLEEPING . = SS_SLEEPING
fire(resumed) fire(resumed)
@@ -87,7 +140,7 @@
queue_node_flags = queue_node.flags queue_node_flags = queue_node.flags
if (queue_node_flags & SS_TICKER) if (queue_node_flags & SS_TICKER)
if (!(SS_flags & SS_TICKER)) if ((SS_flags & (SS_TICKER|SS_BACKGROUND)) != SS_TICKER)
continue continue
if (queue_node_priority < SS_priority) if (queue_node_priority < SS_priority)
break break
@@ -155,6 +208,9 @@
if(SS_SLEEPING) if(SS_SLEEPING)
state = SS_PAUSING state = SS_PAUSING
/// Called after the config has been loaded or reloaded.
/datum/controller/subsystem/proc/OnConfigLoad()
/datum/controller/subsystem/proc/subsystem_log(msg) /datum/controller/subsystem/proc/subsystem_log(msg)
return log_subsystem(name, msg) return log_subsystem(name, msg)
@@ -164,7 +220,7 @@
var/time = (REALTIMEOFDAY - start_timeofday) / 10 var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!" var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!"
to_chat(world, "<span class='boldannounce'>[msg]</span>") to_chat(world, "<span class='boldannounce'>[msg]</span>")
log_subsystem("INIT", msg) log_world(msg)
return time return time
//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc. //hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc.

View File

@@ -353,7 +353,6 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
dat = list("<tt>", header.Join(), body, "<br></tt>") dat = list("<tt>", header.Join(), body, "<br></tt>")
var/datum/browser/popup = new(user, "id_com", src.name, 900, 620) var/datum/browser/popup = new(user, "id_com", src.name, 900, 620)
popup.set_content(dat.Join()) popup.set_content(dat.Join())
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open() popup.open()
/obj/machinery/computer/card/Topic(href, href_list) /obj/machinery/computer/card/Topic(href, href_list)

View File

@@ -1,3 +1,15 @@
#define STATE_DEFAULT 1
#define STATE_CALLSHUTTLE 2
#define STATE_CANCELSHUTTLE 3
#define STATE_MESSAGELIST 4
#define STATE_VIEWMESSAGE 5
#define STATE_DELMESSAGE 6
#define STATE_STATUSDISPLAY 7
#define STATE_ALERT_LEVEL 8
#define STATE_CONFIRM_LEVEL 9
#define STATE_TOGGLE_EMERGENCY 10
#define STATE_PURCHASE 11
// The communications computer // The communications computer
/obj/machinery/computer/communications /obj/machinery/computer/communications
name = "communications console" name = "communications console"
@@ -6,6 +18,7 @@
icon_keyboard = "tech_key" icon_keyboard = "tech_key"
req_access = list(ACCESS_HEADS) req_access = list(ACCESS_HEADS)
circuit = /obj/item/circuitboard/computer/communications circuit = /obj/item/circuitboard/computer/communications
light_color = LIGHT_COLOR_BLUE
var/auth_id = "Unknown" //Who is currently logged in? var/auth_id = "Unknown" //Who is currently logged in?
var/list/datum/comm_message/messages = list() var/list/datum/comm_message/messages = list()
var/datum/comm_message/currmsg var/datum/comm_message/currmsg
@@ -16,22 +29,10 @@
var/ai_message_cooldown = 0 var/ai_message_cooldown = 0
var/tmp_alertlevel = 0 var/tmp_alertlevel = 0
var/static/security_level_cd // used to stop mass spam. var/static/security_level_cd // used to stop mass spam.
var/const/STATE_DEFAULT = 1
var/const/STATE_CALLSHUTTLE = 2
var/const/STATE_CANCELSHUTTLE = 3
var/const/STATE_MESSAGELIST = 4
var/const/STATE_VIEWMESSAGE = 5
var/const/STATE_DELMESSAGE = 6
var/const/STATE_STATUSDISPLAY = 7
var/const/STATE_ALERT_LEVEL = 8
var/const/STATE_CONFIRM_LEVEL = 9
var/const/STATE_TOGGLE_EMERGENCY = 10
var/const/STATE_PURCHASE = 11
var/stat_msg1 var/stat_msg1
var/stat_msg2 var/stat_msg2
light_color = LIGHT_COLOR_BLUE
/obj/machinery/computer/communications/proc/checkCCcooldown() /obj/machinery/computer/communications/proc/checkCCcooldown()
var/obj/item/circuitboard/computer/communications/CM = circuit var/obj/item/circuitboard/computer/communications/CM = circuit
@@ -46,7 +47,7 @@
/obj/machinery/computer/communications/Topic(href, href_list) /obj/machinery/computer/communications/Topic(href, href_list)
if(..()) if(..())
return return
if(!usr.canUseTopic(src)) if(!usr.canUseTopic(src, !issilicon(usr)))
return return
if(!is_station_level(z) && !is_reserved_level(z)) //Can only use in transit and on SS13 if(!is_station_level(z) && !is_reserved_level(z)) //Can only use in transit and on SS13
to_chat(usr, "<span class='boldannounce'>Unable to establish a connection</span>: \black You're too far away from the station!") to_chat(usr, "<span class='boldannounce'>Unable to establish a connection</span>: \black You're too far away from the station!")
@@ -132,15 +133,20 @@
if("crossserver") if("crossserver")
if(authenticated==2) if(authenticated==2)
var/dest = href_list["cross_dest"]
if(!checkCCcooldown()) if(!checkCCcooldown())
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>") to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
return return
var/input = stripped_multiline_input(usr, "Please choose a message to transmit to allied stations. Please be aware that this process is very expensive, and abuse will lead to... termination.", "Send a message to an allied station.", "") var/warning = dest == "all" ? "Please choose a message to transmit to allied stations." : "Please choose a message to transmit to [dest] sector station."
var/input = stripped_multiline_input(usr, "[warning] Please be aware that this process is very expensive, and abuse will lead to... termination.", "Send a message to an allied station.", "")
if(!input || !(usr in view(1,src)) || !checkCCcooldown()) if(!input || !(usr in view(1,src)) || !checkCCcooldown())
return return
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
send2otherserver("[station_name()]", input,"Comms_Console") if(dest == "all")
send2otherserver("[station_name()]", input,"Comms_Console")
else
send2otherserver("[station_name()]", input,"Comms_Console", list(dest))
minor_announce(input, title = "Outgoing message to allied station") minor_announce(input, title = "Outgoing message to allied station")
usr.log_talk(input, LOG_SAY, tag="message to the other server") usr.log_talk(input, LOG_SAY, tag="message to the other server")
message_admins("[ADMIN_LOOKUPFLW(usr)] has sent a message to the other server.") message_admins("[ADMIN_LOOKUPFLW(usr)] has sent a message to the other server.")
@@ -156,12 +162,12 @@
var/datum/map_template/shuttle/S = locate(href_list["chosen_shuttle"]) in shuttles var/datum/map_template/shuttle/S = locate(href_list["chosen_shuttle"]) in shuttles
if(S && istype(S)) if(S && istype(S))
if(SSshuttle.emergency.mode != SHUTTLE_RECALL && SSshuttle.emergency.mode != SHUTTLE_IDLE) if(SSshuttle.emergency.mode != SHUTTLE_RECALL && SSshuttle.emergency.mode != SHUTTLE_IDLE)
to_chat(usr, "It's a bit late to buy a new shuttle, don't you think?") to_chat(usr, "<span class='alert'>It's a bit late to buy a new shuttle, don't you think?</span>")
return return
if(SSshuttle.shuttle_purchased) if(SSshuttle.shuttle_purchased)
to_chat(usr, "A replacement shuttle has already been purchased.") to_chat(usr, "<span class='alert'>A replacement shuttle has already been purchased.</span>")
else if(!S.prerequisites_met()) else if(!S.prerequisites_met())
to_chat(usr, "You have not met the requirements for purchasing this shuttle.") to_chat(usr, "<span class='alert'>You have not met the requirements for purchasing this shuttle.</span>")
else else
var/points_to_check var/points_to_check
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
@@ -183,7 +189,7 @@
if("callshuttle") if("callshuttle")
state = STATE_DEFAULT state = STATE_DEFAULT
if(authenticated) if(authenticated && SSshuttle.canEvac(usr))
state = STATE_CALLSHUTTLE state = STATE_CALLSHUTTLE
if("callshuttle2") if("callshuttle2")
if(authenticated) if(authenticated)
@@ -284,12 +290,12 @@
if("MessageCentCom") if("MessageCentCom")
if(authenticated) if(authenticated)
if(!checkCCcooldown()) if(!checkCCcooldown())
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>") to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
return return
var/input = stripped_input(usr, "Please choose a message to transmit to CentCom via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to CentCom.", "") var/input = stripped_input(usr, "Please choose a message to transmit to CentCom via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to CentCom.", "")
if(!input || !(usr in view(1,src)) || !checkCCcooldown()) if(!input || !(usr in view(1,src)) || !checkCCcooldown())
return return
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
CentCom_announce(input, usr) CentCom_announce(input, usr)
to_chat(usr, "<span class='notice'>Message transmitted to Central Command.</span>") to_chat(usr, "<span class='notice'>Message transmitted to Central Command.</span>")
for(var/client/X in GLOB.admins) for(var/client/X in GLOB.admins)
@@ -302,7 +308,7 @@
// OMG SYNDICATE ...LETTERHEAD // OMG SYNDICATE ...LETTERHEAD
if("MessageSyndicate") if("MessageSyndicate")
if((authenticated==2) && (obj_flags & EMAGGED)) if((authenticated) && (obj_flags & EMAGGED))
if(!checkCCcooldown()) if(!checkCCcooldown())
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>") to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
@@ -332,7 +338,7 @@
if(!checkCCcooldown()) if(!checkCCcooldown())
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>") to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
return return
var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self Destruct Code Request.","") var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self-Destruct Code Request.","")
if(!input || !(usr in view(1,src)) || !checkCCcooldown()) if(!input || !(usr in view(1,src)) || !checkCCcooldown())
return return
Nuke_request(input, usr) Nuke_request(input, usr)
@@ -347,7 +353,9 @@
aicurrmsg = null aicurrmsg = null
aistate = STATE_DEFAULT aistate = STATE_DEFAULT
if("ai-callshuttle") if("ai-callshuttle")
aistate = STATE_CALLSHUTTLE aistate = STATE_DEFAULT
if(SSshuttle.canEvac(usr))
aistate = STATE_CALLSHUTTLE
if("ai-callshuttle2") if("ai-callshuttle2")
SSshuttle.requestEvac(usr, href_list["call"]) SSshuttle.requestEvac(usr, href_list["call"])
aistate = STATE_DEFAULT aistate = STATE_DEFAULT
@@ -461,7 +469,7 @@
var/datum/browser/popup = new(user, "communications", "Communications Console", 400, 500) var/datum/browser/popup = new(user, "communications", "Communications Console", 400, 500)
if(issilicon(user) || (hasSiliconAccessInArea(user) && !in_range(user,src))) if(issilicon(user))
var/dat2 = interact_ai(user) // give the AI a different interact proc to limit its access var/dat2 = interact_ai(user) // give the AI a different interact proc to limit its access
if(dat2) if(dat2)
dat += dat2 dat += dat2
@@ -492,9 +500,15 @@
if (authenticated==2) if (authenticated==2)
dat += "<BR><BR><B>Captain Functions</B>" dat += "<BR><BR><B>Captain Functions</B>"
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=announce'>Make a Captain's Announcement</A> \]" dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=announce'>Make a Captain's Announcement</A> \]"
var/cross_servers_count = length(CONFIG_GET(keyed_list/cross_server)) var/list/cross_servers = CONFIG_GET(keyed_list/cross_server)
if(cross_servers_count) var/our_id = CONFIG_GET(string/cross_comms_name)
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=crossserver'>Send a message to [cross_servers_count == 1 ? "an " : ""]allied station[cross_servers_count > 1 ? "s" : ""]</A> \]" if(cross_servers.len)
for(var/server in cross_servers)
if(server == our_id)
continue
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=crossserver=all;cross_dest=[server]'>Send a message to station in [server] sector.</A> \]"
if(cross_servers.len > 2)
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=crossserver;cross_dest=all'>Send a message to all allied stations</A> \]"
if(SSmapping.config.allow_custom_shuttles) if(SSmapping.config.allow_custom_shuttles)
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=purchase_menu'>Purchase Shuttle</A> \]" dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=purchase_menu'>Purchase Shuttle</A> \]"
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=changeseclevel'>Change Alert Level</A> \]" dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=changeseclevel'>Change Alert Level</A> \]"
@@ -720,8 +734,13 @@
to_chat(user, "<span class='alert'>Intercomms recharging. Please stand by.</span>") to_chat(user, "<span class='alert'>Intercomms recharging. Please stand by.</span>")
return return
var/input = stripped_input(user, "Please choose a message to announce to the station crew.", "What?") var/input = stripped_input(user, "Please choose a message to announce to the station crew.", "What?")
if(!input || !user.canUseTopic(src)) if(!input || !user.canUseTopic(src, !issilicon(usr)))
return return
if(!(user.can_speak())) //No more cheating, mime/random mute guy!
input = "..."
to_chat(user, "<span class='warning'>You find yourself unable to speak.</span>")
else
input = user.treat_message(input) //Adds slurs and so on. Someone should make this use languages too.
SScommunications.make_announcement(user, is_silicon, input) SScommunications.make_announcement(user, is_silicon, input)
deadchat_broadcast("<span class='deadsay'><span class='name'>[user.real_name]</span> made an priority announcement from <span class='name'>[get_area_name(usr, TRUE)]</span>.</span>", user) deadchat_broadcast("<span class='deadsay'><span class='name'>[user.real_name]</span> made an priority announcement from <span class='name'>[get_area_name(usr, TRUE)]</span>.</span>", user)
@@ -770,3 +789,15 @@
content = new_content content = new_content
if(new_possible_answers) if(new_possible_answers)
possible_answers = new_possible_answers possible_answers = new_possible_answers
#undef STATE_DEFAULT
#undef STATE_CALLSHUTTLE
#undef STATE_CANCELSHUTTLE
#undef STATE_MESSAGELIST
#undef STATE_VIEWMESSAGE
#undef STATE_DELMESSAGE
#undef STATE_STATUSDISPLAY
#undef STATE_ALERT_LEVEL
#undef STATE_CONFIRM_LEVEL
#undef STATE_TOGGLE_EMERGENCY
#undef STATE_PURCHASE

View File

@@ -234,7 +234,6 @@ RLD
var/datum/browser/popup = new(user, "rcd_access", "Access Control", 900, 500, src) var/datum/browser/popup = new(user, "rcd_access", "Access Control", 900, 500, src)
popup.set_content(t1) popup.set_content(t1)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.open() popup.open()
/obj/item/construction/rcd/Topic(href, href_list) /obj/item/construction/rcd/Topic(href, href_list)

View File

@@ -334,8 +334,7 @@
to_chat(C, "<span class='big warning'> You feel an overwhelming desire to [message]") to_chat(C, "<span class='big warning'> You feel an overwhelming desire to [message]")
if(2) if(2)
visible_message("<b>[src]</b> waves their arms around, <span class='spooky'>\"If only you had a better upbringing, your ears are now full of my singing!\"</span>") visible_message("<b>[src]</b> waves their arms around, <span class='spooky'>\"If only you had a better upbringing, your ears are now full of my singing!\"</span>")
var/client/C2 = C.client C.client.tgui_panel?.play_music("https://puu.sh/ExBbv.mp4")
C.tgui_panel?.play_music("https://puu.sh/ExBbv.mp4")
if(3) if(3)
visible_message("<b>[src]</b> waves their arms around, <span class='spooky'>\"You're cute little bumpkin, On your head is a pumpkin!\"</span>") visible_message("<b>[src]</b> waves their arms around, <span class='spooky'>\"You're cute little bumpkin, On your head is a pumpkin!\"</span>")
if(C.head) if(C.head)

View File

@@ -82,7 +82,6 @@
var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500) var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
popup.set_content(dat.Join("")) popup.set_content(dat.Join(""))
popup.set_title_image(user.browse_rsc_icon(parent.icon, parent.icon_state))
popup.open() popup.open()
/datum/song/proc/ParseSong(text) /datum/song/proc/ParseSong(text)

View File

@@ -12,10 +12,10 @@
/mob/living/carbon/human/movement_delay() /mob/living/carbon/human/movement_delay()
. = ..() . = ..()
if(CHECK_MOBILITY(src, MOBILITY_STAND) && m_intent == MOVE_INTENT_RUN && (combat_flags & COMBAT_FLAG_SPRINT_ACTIVE)) if(CHECK_MOBILITY(src, MOBILITY_STAND) && m_intent == MOVE_INTENT_RUN && (combat_flags & COMBAT_FLAG_SPRINT_ACTIVE))
var/static/datum/config_entry/number/movedelay/sprint_speed_increase/SSI var/static/SSI
if(!SSI) if(!SSI)
SSI = CONFIG_GET_ENTRY(number/movedelay/sprint_speed_increase) SSI = CONFIG_GET(number/movedelay/sprint_speed_increase)
. -= SSI.config_entry_value . -= SSI //but WHY
if (m_intent == MOVE_INTENT_WALK && HAS_TRAIT(src, TRAIT_SPEEDY_STEP)) if (m_intent == MOVE_INTENT_WALK && HAS_TRAIT(src, TRAIT_SPEEDY_STEP))
. -= 1.5 . -= 1.5

View File

@@ -1,12 +1,20 @@
/* Photocopiers!
* Contains:
* Photocopier
* Toner Cartridge
*/
/* /// For use with the `color_mode` var. Photos will be printed in greyscale while the var has this value.
* Photocopier #define PHOTO_GREYSCALE "Greyscale"
*/ /// For use with the `color_mode` var. Photos will be printed in full color while the var has this value.
#define PHOTO_COLOR "Color"
/// How much toner is used for making a copy of a paper.
#define PAPER_TONER_USE 0.125
/// How much toner is used for making a copy of a photo.
#define PHOTO_TONER_USE 0.625
/// How much toner is used for making a copy of a document.
#define DOCUMENT_TONER_USE 0.75
/// How much toner is used for making a copy of an ass.
#define ASS_TONER_USE 0.625
/// The maximum amount of copies you can make with one press of the copy button.
#define MAX_COPIES_AT_ONCE 10
/obj/machinery/photocopier /obj/machinery/photocopier
name = "photocopier" name = "photocopier"
desc = "Used to copy important documents and anatomy studies." desc = "Used to copy important documents and anatomy studies."
@@ -19,183 +27,314 @@
power_channel = EQUIP power_channel = EQUIP
max_integrity = 300 max_integrity = 300
integrity_failure = 0.33 integrity_failure = 0.33
var/obj/item/paper/copy = null //what's in the copier! /// A reference to an `/obj/item/paper` inside the copier, if one is inserted. Otherwise null.
var/obj/item/photo/photocopy = null var/obj/item/paper/paper_copy
var/obj/item/documents/doccopy = null /// A reference to an `/obj/item/photo` inside the copier, if one is inserted. Otherwise null.
var/copies = 1 //how many copies to print! var/obj/item/photo/photo_copy
var/toner = 40 //how much toner is left! woooooo~ /// A reference to an `/obj/item/documents` inside the copier, if one is inserted. Otherwise null.
var/maxcopies = 10 //how many copies can be copied at once- idea shamelessly stolen from bs12's copier! var/obj/item/documents/document_copy
var/greytoggle = "Greyscale" /// A reference to a mob on top of the photocopier trying to copy their ass. Null if there is no mob.
var/mob/living/ass //i can't believe i didn't write a stupid-ass comment about this var when i first coded asscopy. var/mob/living/ass
/// A reference to the toner cartridge that's inserted into the copier. Null if there is no cartridge.
var/obj/item/toner/toner_cartridge
/// How many copies will be printed with one click of the "copy" button.
var/num_copies = 1
/// Used with photos. Determines if the copied photo will be in greyscale or color.
var/color_mode = PHOTO_COLOR
/// Indicates whether the printer is currently busy copying or not.
var/busy = FALSE var/busy = FALSE
/obj/machinery/photocopier/ui_interact(mob/user) /obj/machinery/photocopier/Initialize()
. = ..() . = ..()
var/list/dat = list("Photocopier<BR><BR>") //AddComponent(/datum/component/payment, 5, SSeconomy.get_dep_account(ACCOUNT_CIV), PAYMENT_CLINICAL)
if(copy || photocopy || doccopy || (ass && (ass.loc == src.loc))) toner_cartridge = new(src)
dat += "<a href='byond://?src=[REF(src)];remove=1'>Remove Paper</a><BR>"
if(toner)
dat += "<a href='byond://?src=[REF(src)];copy=1'>Copy</a><BR>"
dat += "Printing: [copies] copies."
dat += "<a href='byond://?src=[REF(src)];min=1'>-</a> "
dat += "<a href='byond://?src=[REF(src)];add=1'>+</a><BR><BR>"
if(photocopy)
dat += "Printing in <a href='byond://?src=[REF(src)];colortoggle=1'>[greytoggle]</a><BR><BR>"
else if(toner)
dat += "Please insert paper to copy.<BR><BR>"
if(isAI(user))
dat += "<a href='byond://?src=[REF(src)];aipic=1'>Print photo from database</a><BR><BR>"
dat += "Current toner level: [toner]"
if(!toner)
dat +="<BR>Please insert a new toner cartridge!"
user << browse(dat.Join(""), "window=copier")
onclose(user, "copier")
/obj/machinery/photocopier/Topic(href, href_list) /obj/machinery/photocopier/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Photocopier")
ui.open()
/obj/machinery/photocopier/ui_data(mob/user)
var/list/data = list()
data["has_item"] = !copier_empty()
data["num_copies"] = num_copies
if(photo_copy)
data["is_photo"] = TRUE
data["color_mode"] = color_mode
if(isAI(user))
data["isAI"] = TRUE
data["can_AI_print"] = toner_cartridge ? toner_cartridge.charges >= PHOTO_TONER_USE : FALSE
else
data["isAI"] = FALSE
if(toner_cartridge)
data["has_toner"] = TRUE
data["current_toner"] = toner_cartridge.charges
data["max_toner"] = toner_cartridge.max_charges
data["has_enough_toner"] = has_enough_toner()
else
data["has_toner"] = FALSE
data["has_enough_toner"] = FALSE
return data
/obj/machinery/photocopier/ui_act(action, list/params)
if(..()) if(..())
return return
if(href_list["copy"])
if(copy) switch(action)
for(var/i = 0, i < copies, i++) // Copying paper, photos, documents and asses.
if(toner > 0 && !busy && copy) if("make_copy")
var/copy_as_paper = 1 if(busy)
if(istype(copy, /obj/item/paper/contract/employment)) to_chat(usr, "<span class='warning'>[src] is currently busy copying something. Please wait until it is finished.</span>")
var/obj/item/paper/contract/employment/E = copy return FALSE
var/obj/item/paper/contract/employment/C = new /obj/item/paper/contract/employment (loc, E.target.current) if(paper_copy)
if(C) if(!length(paper_copy.info))
copy_as_paper = 0 to_chat(usr, "<span class='warning'>An error message flashes across [src]'s screen: \"The supplied paper is blank. Aborting.\"</span>")
if(copy_as_paper) return FALSE
var/obj/item/paper/c = new /obj/item/paper (loc) // Basic paper
if(length(copy.info) > 0) //Only print and add content if the copied doc has words on it if(istype(paper_copy, /obj/item/paper))
if(toner > 10) //lots of toner, make it dark do_copy_loop(CALLBACK(src, .proc/make_paper_copy), usr)
c.info = "<font color = #101010>" return TRUE
else //no toner? shitty copies for you! // Devil contract paper.
c.info = "<font color = #808080>" if(istype(paper_copy, /obj/item/paper/contract/employment))
var/copied = copy.info do_copy_loop(CALLBACK(src, .proc/make_devil_paper_copy), usr)
copied = replacetext(copied, "<font face=\"[PEN_FONT]\" color=", "<font face=\"[PEN_FONT]\" nocolor=") //state of the art techniques in action return TRUE
copied = replacetext(copied, "<font face=\"[CRAYON_FONT]\" color=", "<font face=\"[CRAYON_FONT]\" nocolor=") //This basically just breaks the existing color tag, which we need to do because the innermost tag takes priority. // Copying photo.
c.info += copied if(photo_copy)
c.info += "</font>" do_copy_loop(CALLBACK(src, .proc/make_photo_copy), usr)
c.name = copy.name return TRUE
c.update_icon() // Copying Documents.
c.stamps = copy.stamps if(document_copy)
if(copy.stamped) do_copy_loop(CALLBACK(src, .proc/make_document_copy), usr)
c.stamped = copy.stamped.Copy() return TRUE
c.copy_overlays(copy, TRUE) // ASS COPY. By Miauw
toner-- if(ass)
busy = TRUE do_copy_loop(CALLBACK(src, .proc/make_ass_copy), usr)
addtimer(CALLBACK(src, .proc/reset_busy), 1.5 SECONDS) return TRUE
else
break // Remove the paper/photo/document from the photocopier.
updateUsrDialog() if("remove")
else if(photocopy) if(paper_copy)
for(var/i = 0, i < copies, i++) remove_photocopy(paper_copy, usr)
if(toner >= 5 && !busy && photocopy) //Was set to = 0, but if there was say 3 toner left and this ran, you would get -2 which would be weird for ink paper_copy = null
new /obj/item/photo (loc, photocopy.picture.Copy(greytoggle == "Greyscale"? TRUE : FALSE)) else if(photo_copy)
busy = TRUE remove_photocopy(photo_copy, usr)
addtimer(CALLBACK(src, .proc/reset_busy), 1.5 SECONDS) photo_copy = null
else else if(document_copy)
break remove_photocopy(document_copy, usr)
else if(doccopy) document_copy = null
for(var/i = 0, i < copies, i++) else if(check_ass())
if(toner > 5 && !busy && doccopy) to_chat(ass, "<span class='notice'>You feel a slight pressure on your ass.</span>")
new /obj/item/documents/photocopy(loc, doccopy) return TRUE
toner-= 6 // the sprite shows 6 papers, yes I checked
busy = TRUE // AI printing photos from their saved images.
addtimer(CALLBACK(src, .proc/reset_busy), 1.5 SECONDS) if("ai_photo")
else if(busy)
break to_chat(usr, "<span class='warning'>[src] is currently busy copying something. Please wait until it is finished.</span>")
updateUsrDialog() return FALSE
else if(ass) //ASS COPY. By Miauw
for(var/i = 0, i < copies, i++)
var/icon/temp_img
if(ishuman(ass) && (ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) || ass.get_item_by_slot(ITEM_SLOT_OCLOTHING)))
to_chat(usr, "<span class='notice'>You feel kind of silly, copying [ass == usr ? "your" : ass][ass == usr ? "" : "\'s"] ass with [ass == usr ? "your" : "[ass.p_their()]"] clothes on.</span>" )
break
else if(toner >= 5 && !busy && check_ass()) //You have to be sitting on the copier and either be a xeno or a human without clothes on.
if(isalienadult(ass) || istype(ass, /mob/living/simple_animal/hostile/alien)) //Xenos have their own asses, thanks to Pybro.
temp_img = icon('icons/ass/assalien.png')
else if(ishuman(ass)) //Suit checks are in check_ass
temp_img = icon(ass.gender == FEMALE ? 'icons/ass/assfemale.png' : 'icons/ass/assmale.png')
else if(isdrone(ass)) //Drones are hot
temp_img = icon('icons/ass/assdrone.png')
else
break
busy = TRUE
sleep(15)
var/obj/item/photo/p = new /obj/item/photo (loc)
var/datum/picture/toEmbed = new(name = "[ass]'s Ass", desc = "You see [ass]'s ass on the photo.", image = temp_img)
p.pixel_x = rand(-10, 10)
p.pixel_y = rand(-10, 10)
toEmbed.psize_x = 128
toEmbed.psize_y = 128
p.set_picture(toEmbed, TRUE, TRUE)
toner -= 5
busy = FALSE
else
break
updateUsrDialog()
else if(href_list["remove"])
if(copy)
remove_photocopy(copy, usr)
copy = null
else if(photocopy)
remove_photocopy(photocopy, usr)
photocopy = null
else if(doccopy)
remove_photocopy(doccopy, usr)
doccopy = null
else if(check_ass())
to_chat(ass, "<span class='notice'>You feel a slight pressure on your ass.</span>")
updateUsrDialog()
else if(href_list["min"])
if(copies > 1)
copies--
updateUsrDialog()
else if(href_list["add"])
if(copies < maxcopies)
copies++
updateUsrDialog()
else if(href_list["aipic"])
if(!isAI(usr))
return
if(toner >= 5 && !busy)
var/mob/living/silicon/ai/tempAI = usr var/mob/living/silicon/ai/tempAI = usr
if(tempAI.aicamera.stored.len == 0) if(!length(tempAI.aicamera.stored))
to_chat(usr, "<span class='boldannounce'>No images saved</span>") to_chat(usr, "<span class='boldannounce'>No images saved.</span>")
return return
var/datum/picture/selection = tempAI.aicamera.selectpicture(usr) var/datum/picture/selection = tempAI.aicamera.selectpicture(usr)
var/obj/item/photo/photo = new(loc, selection) var/obj/item/photo/photo = new(loc, selection) // AI prints color photos only.
photo.pixel_x = rand(-10, 10) give_pixel_offset(photo)
photo.pixel_y = rand(-10, 10) toner_cartridge.charges -= PHOTO_TONER_USE
toner -= 5 //AI prints color pictures only, thus they can do it more efficiently return TRUE
busy = TRUE
addtimer(CALLBACK(src, .proc/reset_busy), 1.5 SECONDS)
updateUsrDialog()
else if(href_list["colortoggle"])
if(greytoggle == "Greyscale")
greytoggle = "Color"
else
greytoggle = "Greyscale"
updateUsrDialog()
// Switch between greyscale and color photos
if("color_mode")
if(params["mode"] in list(PHOTO_GREYSCALE, PHOTO_COLOR))
color_mode = params["mode"]
return TRUE
// Remove the toner cartridge from the copier.
if("remove_toner")
if(issilicon(usr) || (ishuman(usr) && !usr.put_in_hands(toner_cartridge)))
toner_cartridge.forceMove(drop_location())
toner_cartridge = null
return TRUE
// Set the number of copies to be printed with 1 click of the "copy" button.
if("set_copies")
num_copies = clamp(text2num(params["num_copies"]), 1, MAX_COPIES_AT_ONCE)
return TRUE
/**
* Determines if the photocopier has enough toner to create `num_copies` amount of copies of the currently inserted item.
*/
/obj/machinery/photocopier/proc/has_enough_toner()
if(paper_copy)
return toner_cartridge.charges >= (PAPER_TONER_USE * num_copies)
else if(document_copy)
return toner_cartridge.charges >= (DOCUMENT_TONER_USE * num_copies)
else if(photo_copy)
return toner_cartridge.charges >= (PHOTO_TONER_USE * num_copies)
else if(ass)
return toner_cartridge.charges >= (ASS_TONER_USE * num_copies)
return FALSE
/**
* Will invoke the passed in `copy_cb` callback in 1 second intervals, and charge the user 5 credits for each copy made.
*
* Arguments:
* * copy_cb - a callback for which proc to call. Should only be one of the `make_x_copy()` procs, such as `make_paper_copy()`.
* * user - the mob who clicked copy.
*/
/obj/machinery/photocopier/proc/do_copy_loop(datum/callback/copy_cb, mob/user)
busy = TRUE
var/i
for(i in 1 to num_copies)
//if(attempt_charge(src, user) & COMPONENT_OBJ_CANCEL_CHARGE)
// break
addtimer(copy_cb, i SECONDS)
addtimer(CALLBACK(src, .proc/reset_busy), i SECONDS)
/**
* Sets busy to `FALSE`. Created as a proc so it can be used in callbacks.
*/
/obj/machinery/photocopier/proc/reset_busy() /obj/machinery/photocopier/proc/reset_busy()
busy = FALSE busy = FALSE
updateUsrDialog()
/obj/machinery/photocopier/proc/do_insertion(obj/item/O, mob/user) /**
O.forceMove(src) * Gives items a random x and y pixel offset, between -10 and 10 for each.
to_chat(user, "<span class='notice'>You insert [O] into [src].</span>") *
* This is done that when someone prints multiple papers, we dont have them all appear to be stacked in the same exact location.
*
* Arguments:
* * copied_item - The paper, document, or photo that was just spawned on top of the printer.
*/
/obj/machinery/photocopier/proc/give_pixel_offset(obj/item/copied_item)
copied_item.pixel_x = rand(-10, 10)
copied_item.pixel_y = rand(-10, 10)
/**
* Handles the copying of devil contract paper. Transfers all the text, stamps and so on from the old paper, to the copy.
*
* Checks first if `paper_copy` exists. Since this proc is called from a timer, it's possible that it was removed.
* Does not check if it has enough toner because devil contracts cost no toner to print.
*/
/obj/machinery/photocopier/proc/make_devil_paper_copy()
if(!paper_copy)
return
var/obj/item/paper/contract/employment/E = paper_copy
var/obj/item/paper/contract/employment/C = new(loc, E.target.current)
give_pixel_offset(C)
/**
* Handles the copying of paper. Transfers all the text, stamps and so on from the old paper, to the copy.
*
* Checks first if `paper_copy` exists. Since this proc is called from a timer, it's possible that it was removed.
*/
/obj/machinery/photocopier/proc/make_paper_copy()
if(!paper_copy)
return
var/obj/item/paper/copied_paper = new(loc)
give_pixel_offset(copied_paper)
if(toner_cartridge.charges > 10) // Lots of toner, make it dark.
copied_paper.info = "<font color = #101010>"
else // No toner? shitty copies for you!
copied_paper.info = "<font color = #808080>"
var/copied_info = paper_copy.info
copied_info = replacetext(copied_info, "<font face=\"[PEN_FONT]\" color=", "<font face=\"[PEN_FONT]\" nocolor=") //state of the art techniques in action
copied_info = replacetext(copied_info, "<font face=\"[CRAYON_FONT]\" color=", "<font face=\"[CRAYON_FONT]\" nocolor=") //This basically just breaks the existing color tag, which we need to do because the innermost tag takes priority.
copied_paper.info += copied_info
copied_paper.info += "</font>"
copied_paper.name = paper_copy.name
copied_paper.update_icon()
copied_paper.stamps = paper_copy.stamps
if(paper_copy.stamped)
copied_paper.stamped = paper_copy.stamped.Copy()
copied_paper.copy_overlays(paper_copy, TRUE)
toner_cartridge.charges -= PAPER_TONER_USE
/**
* Handles the copying of photos, which can be printed in either color or greyscale.
*
* Checks first if `photo_copy` exists. Since this proc is called from a timer, it's possible that it was removed.
*/
/obj/machinery/photocopier/proc/make_photo_copy()
if(!photo_copy)
return
var/obj/item/photo/copied_pic = new(loc, photo_copy.picture.Copy(color_mode == PHOTO_GREYSCALE ? TRUE : FALSE))
give_pixel_offset(copied_pic)
toner_cartridge.charges -= PHOTO_TONER_USE
/**
* Handles the copying of documents.
*
* Checks first if `document_copy` exists. Since this proc is called from a timer, it's possible that it was removed.
*/
/obj/machinery/photocopier/proc/make_document_copy()
if(!document_copy)
return
var/obj/item/documents/photocopy/copied_doc = new(loc, document_copy)
give_pixel_offset(copied_doc)
toner_cartridge.charges -= DOCUMENT_TONER_USE
/**
* Handles the copying of an ass photo.
*
* Calls `check_ass()` first to make sure that `ass` exists, among other conditions. Since this proc is called from a timer, it's possible that it was removed.
* Additionally checks that the mob has their clothes off.
*/
/obj/machinery/photocopier/proc/make_ass_copy()
if(!check_ass())
return
if(ishuman(ass) && (ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) || ass.get_item_by_slot(ITEM_SLOT_OCLOTHING)))
to_chat(usr, "<span class='notice'>You feel kind of silly, copying [ass == usr ? "your" : ass][ass == usr ? "" : "\'s"] ass with [ass == usr ? "your" : "[ass.p_their()]"] clothes on.</span>" )
return
var/icon/temp_img
if(isalienadult(ass) || istype(ass, /mob/living/simple_animal/hostile/alien)) //Xenos have their own asses, thanks to Pybro.
temp_img = icon('icons/ass/assalien.png')
else if(ishuman(ass)) //Suit checks are in check_ass
temp_img = icon(ass.gender == FEMALE ? 'icons/ass/assfemale.png' : 'icons/ass/assmale.png')
else if(isdrone(ass)) //Drones are hot
temp_img = icon('icons/ass/assdrone.png')
var/obj/item/photo/copied_ass = new /obj/item/photo(loc)
var/datum/picture/toEmbed = new(name = "[ass]'s Ass", desc = "You see [ass]'s ass on the photo.", image = temp_img)
give_pixel_offset(copied_ass)
toEmbed.psize_x = 128
toEmbed.psize_y = 128
copied_ass.set_picture(toEmbed, TRUE, TRUE)
toner_cartridge.charges -= ASS_TONER_USE
/**
* Inserts the item into the copier. Called in `attackby()` after a human mob clicked on the copier with a paper, photo, or document.
*
* Arugments:
* * object - the object that got inserted.
* * user - the mob that inserted the object.
*/
/obj/machinery/photocopier/proc/do_insertion(obj/item/object, mob/user)
object.forceMove(src)
to_chat(user, "<span class='notice'>You insert [object] into [src].</span>")
flick("photocopier1", src) flick("photocopier1", src)
updateUsrDialog()
/obj/machinery/photocopier/proc/remove_photocopy(obj/item/O, mob/user) /**
* Called when someone hits the "remove item" button on the copier UI.
*
* If the user is a silicon, it drops the object at the location of the copier. If the user is not a silicon, it tries to put the object in their hands first.
* Sets `busy` to `FALSE` because if the inserted item is removed, the copier should halt copying.
*
* Arguments:
* * object - the item we're trying to remove.
* * user - the user removing the item.
*/
/obj/machinery/photocopier/proc/remove_photocopy(obj/item/object, mob/user)
if(!issilicon(user)) //surprised this check didn't exist before, putting stuff in AI's hand is bad if(!issilicon(user)) //surprised this check didn't exist before, putting stuff in AI's hand is bad
O.forceMove(user.loc) object.forceMove(user.loc)
user.put_in_hands(O) user.put_in_hands(object)
else else
O.forceMove(drop_location()) object.forceMove(drop_location())
to_chat(user, "<span class='notice'>You take [O] out of [src].</span>") to_chat(user, "<span class='notice'>You take [object] out of [src]. [busy ? "The [src] comes to a halt." : ""]</span>")
/obj/machinery/photocopier/attackby(obj/item/O, mob/user, params) /obj/machinery/photocopier/attackby(obj/item/O, mob/user, params)
if(default_unfasten_wrench(user, O)) if(default_unfasten_wrench(user, O))
@@ -210,7 +349,7 @@
else else
if(!user.temporarilyRemoveItemFromInventory(O)) if(!user.temporarilyRemoveItemFromInventory(O))
return return
copy = O paper_copy = O
do_insertion(O, user) do_insertion(O, user)
else else
to_chat(user, "<span class='warning'>There is already something in [src]!</span>") to_chat(user, "<span class='warning'>There is already something in [src]!</span>")
@@ -219,7 +358,7 @@
if(copier_empty()) if(copier_empty())
if(!user.temporarilyRemoveItemFromInventory(O)) if(!user.temporarilyRemoveItemFromInventory(O))
return return
photocopy = O photo_copy = O
do_insertion(O, user) do_insertion(O, user)
else else
to_chat(user, "<span class='warning'>There is already something in [src]!</span>") to_chat(user, "<span class='warning'>There is already something in [src]!</span>")
@@ -228,38 +367,35 @@
if(copier_empty()) if(copier_empty())
if(!user.temporarilyRemoveItemFromInventory(O)) if(!user.temporarilyRemoveItemFromInventory(O))
return return
doccopy = O document_copy = O
do_insertion(O, user) do_insertion(O, user)
else else
to_chat(user, "<span class='warning'>There is already something in [src]!</span>") to_chat(user, "<span class='warning'>There is already something in [src]!</span>")
else if(istype(O, /obj/item/toner)) else if(istype(O, /obj/item/toner))
if(toner <= 0) if(toner_cartridge)
if(!user.temporarilyRemoveItemFromInventory(O)) to_chat(user, "<span class='warning'>[src] already has a toner cartridge inserted. Remove that one first.</span>")
return return
qdel(O) O.forceMove(src)
toner = 40 toner_cartridge = O
to_chat(user, "<span class='notice'>You insert [O] into [src].</span>") to_chat(user, "<span class='notice'>You insert [O] into [src].</span>")
updateUsrDialog()
else
to_chat(user, "<span class='warning'>This cartridge is not yet ready for replacement! Use up the rest of the toner.</span>")
else if(istype(O, /obj/item/areaeditor/blueprints)) else if(istype(O, /obj/item/areaeditor/blueprints))
to_chat(user, "<span class='warning'>The Blueprint is too large to put into the copier. You need to find something else to record the document</span>") to_chat(user, "<span class='warning'>The Blueprint is too large to put into the copier. You need to find something else to record the document.</span>")
else else
return ..() return ..()
/obj/machinery/photocopier/obj_break(damage_flag) /obj/machinery/photocopier/obj_break(damage_flag)
. = ..() . = ..()
if(. && toner > 0) if(. && toner_cartridge.charges)
new /obj/effect/decal/cleanable/oil(get_turf(src)) new /obj/effect/decal/cleanable/oil(get_turf(src))
toner = 0 toner_cartridge.charges = 0
/obj/machinery/photocopier/MouseDrop_T(mob/target, mob/user) /obj/machinery/photocopier/MouseDrop_T(mob/target, mob/user)
check_ass() //Just to make sure that you can re-drag somebody onto it after they moved off. check_ass() //Just to make sure that you can re-drag somebody onto it after they moved off.
if (!istype(target) || target.anchored || target.buckled || !Adjacent(target) || !user.canUseTopic(src, BE_CLOSE) || target == ass || copier_blocked()) if(!istype(target) || target.anchored || target.buckled || !Adjacent(target) || !user.canUseTopic(src, BE_CLOSE) || target == ass || copier_blocked())
return return
src.add_fingerprint(user) add_fingerprint(user)
if(target == user) if(target == user)
user.visible_message("<span class='notice'>[user] starts climbing onto the photocopier!</span>", "<span class='notice'>You start climbing onto the photocopier...</span>") user.visible_message("<span class='notice'>[user] starts climbing onto the photocopier!</span>", "<span class='notice'>You start climbing onto the photocopier...</span>")
else else
@@ -277,49 +413,63 @@
target.forceMove(drop_location()) target.forceMove(drop_location())
ass = target ass = target
if(photocopy) if(photo_copy)
photocopy.forceMove(drop_location()) photo_copy.forceMove(drop_location())
visible_message("<span class='warning'>[photocopy] is shoved out of the way by [ass]!</span>") visible_message("<span class='warning'>[photo_copy] is shoved out of the way by [ass]!</span>")
photocopy = null photo_copy = null
else if(copy) else if(paper_copy)
copy.forceMove(drop_location()) paper_copy.forceMove(drop_location())
visible_message("<span class='warning'>[copy] is shoved out of the way by [ass]!</span>") visible_message("<span class='warning'>[paper_copy] is shoved out of the way by [ass]!</span>")
copy = null paper_copy = null
updateUsrDialog()
else if(document_copy)
document_copy.forceMove(drop_location())
visible_message("<span class='warning'>[document_copy] is shoved out of the way by [ass]!</span>")
document_copy = null
/obj/machinery/photocopier/Exited(atom/movable/AM, atom/newloc)
check_ass() // There was potentially a person sitting on the copier, check if they're still there.
return ..()
/**
* Checks the living mob `ass` exists and its location is the same as the photocopier.
*
* Returns FALSE if `ass` doesn't exist or is not at the copier's location. Returns TRUE otherwise.
*/
/obj/machinery/photocopier/proc/check_ass() //I'm not sure wether I made this proc because it's good form or because of the name. /obj/machinery/photocopier/proc/check_ass() //I'm not sure wether I made this proc because it's good form or because of the name.
if(!ass) if(!ass)
return 0 return FALSE
if(ass.loc != src.loc) if(ass.loc != loc)
ass = null ass = null
updateUsrDialog() return FALSE
return 0 return TRUE
else if(ishuman(ass))
if(!ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) && !ass.get_item_by_slot(ITEM_SLOT_OCLOTHING))
return 1
else
return 0
else
return 1
/**
* Checks if the copier is deleted, or has something dense at its location. Called in `MouseDrop_T()`
*/
/obj/machinery/photocopier/proc/copier_blocked() /obj/machinery/photocopier/proc/copier_blocked()
if(QDELETED(src)) if(QDELETED(src))
return return
if(loc.density) if(loc.density)
return 1 return TRUE
for(var/atom/movable/AM in loc) for(var/atom/movable/AM in loc)
if(AM == src) if(AM == src)
continue continue
if(AM.density) if(AM.density)
return 1 return TRUE
return 0 return FALSE
/**
* Checks if there is an item inserted into the copier or a mob sitting on top of it.
*
* Return `FALSE` is the copier has something inside of it. Returns `TRUE` if it doesn't.
*/
/obj/machinery/photocopier/proc/copier_empty() /obj/machinery/photocopier/proc/copier_empty()
if(copy || photocopy || check_ass()) if(paper_copy || photo_copy || document_copy || check_ass())
return 0 return FALSE
else else
return 1 return TRUE
/* /*
* Toner cartridge * Toner cartridge
@@ -343,3 +493,11 @@
desc = "Why would ANYONE need THIS MUCH TONER?" desc = "Why would ANYONE need THIS MUCH TONER?"
charges = 200 charges = 200
max_charges = 200 max_charges = 200
#undef PHOTO_GREYSCALE
#undef PHOTO_COLOR
#undef PAPER_TONER_USE
#undef PHOTO_TONER_USE
#undef DOCUMENT_TONER_USE
#undef ASS_TONER_USE
#undef MAX_COPIES_AT_ONCE

View File

@@ -33,7 +33,6 @@
var/datum/browser/popup = new(user, "computer", M ? M.name : "shuttle", 300, 200) var/datum/browser/popup = new(user, "computer", M ? M.name : "shuttle", 300, 200)
popup.set_content("<center>[dat]</center>") popup.set_content("<center>[dat]</center>")
popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state))
popup.open() popup.open()
/obj/machinery/computer/shuttle/Topic(href, href_list) /obj/machinery/computer/shuttle/Topic(href, href_list)

1
config/policy.json Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -3399,6 +3399,7 @@
#include "code\modules\tgui\states\always.dm" #include "code\modules\tgui\states\always.dm"
#include "code\modules\tgui\states\conscious.dm" #include "code\modules\tgui\states\conscious.dm"
#include "code\modules\tgui\states\contained.dm" #include "code\modules\tgui\states\contained.dm"
#include "code\modules\tgui\states\debug.dm"
#include "code\modules\tgui\states\deep_inventory.dm" #include "code\modules\tgui\states\deep_inventory.dm"
#include "code\modules\tgui\states\default.dm" #include "code\modules\tgui\states\default.dm"
#include "code\modules\tgui\states\hands.dm" #include "code\modules\tgui\states\hands.dm"