Merge remote-tracking branch 'refs/remotes/polaris-upstream/master' into polaris-sync-2018-01-04
# Conflicts: # code/game/objects/items/devices/communicator/communicator.dm # code/modules/client/preference_setup/occupation/occupation.dm # code/modules/mob/living/simple_animal/animals/cat.dm # code/modules/mob/mob_helpers.dm # code/unit_tests/zas_tests.dm # maps/southern_cross/southern_cross-1.dmm # maps/southern_cross/southern_cross-3.dmm # maps/southern_cross/southern_cross-6.dmm # vorestation.dme
@@ -180,7 +180,6 @@
|
||||
/obj/machinery/atmospherics/tvalve/process()
|
||||
..()
|
||||
. = PROCESS_KILL
|
||||
//machines.Remove(src)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var/global/list/datum/pipe_network/pipe_networks = list()
|
||||
var/global/list/datum/pipe_network/pipe_networks = list() // TODO - Move into SSmachines
|
||||
|
||||
datum/pipe_network
|
||||
/datum/pipe_network
|
||||
var/list/datum/gas_mixture/gases = list() //All of the gas_mixtures continuously connected in this network
|
||||
var/volume = 0 //caches the total volume for atmos machines to use in gas calculations
|
||||
|
||||
@@ -11,13 +11,8 @@ datum/pipe_network
|
||||
var/update = 1
|
||||
//var/datum/gas_mixture/air_transient = null
|
||||
|
||||
New()
|
||||
//air_transient = new()
|
||||
|
||||
..()
|
||||
|
||||
Destroy()
|
||||
pipe_networks -= src
|
||||
STOP_PROCESSING_PIPENET(src)
|
||||
for(var/datum/pipeline/line_member in line_members)
|
||||
line_member.network = null
|
||||
for(var/obj/machinery/atmospherics/normal_member in normal_members)
|
||||
@@ -41,13 +36,14 @@ datum/pipe_network
|
||||
|
||||
if(!start_normal)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
start_normal.network_expand(src, reference)
|
||||
|
||||
update_network_gases()
|
||||
|
||||
if((normal_members.len>0)||(line_members.len>0))
|
||||
pipe_networks += src
|
||||
START_PROCESSING_PIPENET(src)
|
||||
else
|
||||
qdel(src)
|
||||
|
||||
|
||||
@@ -128,6 +128,16 @@ Total Active Edges: [active_edges.len ? "<span class='danger'>[active_edges.len]
|
||||
Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_count]
|
||||
</span>"}, R_DEBUG)
|
||||
|
||||
// Uncomment this if you're having problems finding where active edges are.
|
||||
/*
|
||||
for(var/connection_edge/E in active_edges)
|
||||
world << "Edge became active: [E]."
|
||||
var/i = 1
|
||||
for(var/turf/T in E.connecting_turfs)
|
||||
world << "[i] [T]:[T.x],[T.y],[T.z]"
|
||||
i++
|
||||
*/
|
||||
|
||||
|
||||
// spawn Start()
|
||||
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
#define MC_TICK_CHECK ( ( world.tick_usage > Master.current_ticklimit || src.state != SS_RUNNING ) ? pause() : 0 )
|
||||
#define MC_TICK_CHECK ( ( TICK_USAGE > Master.current_ticklimit || src.state != SS_RUNNING ) ? pause() : 0 )
|
||||
|
||||
// Used for splitting up your remaining time into phases, if you want to evenly divide it.
|
||||
#define MC_SPLIT_TICK_INIT(phase_count) var/original_tick_limit = Master.current_ticklimit; var/split_tick_phases = ##phase_count
|
||||
#define MC_SPLIT_TICK \
|
||||
if(split_tick_phases > 1){\
|
||||
Master.current_ticklimit = ((original_tick_limit - world.tick_usage) / split_tick_phases) + world.tick_usage;\
|
||||
Master.current_ticklimit = ((original_tick_limit - TICK_USAGE) / split_tick_phases) + TICK_USAGE;\
|
||||
--split_tick_phases;\
|
||||
} else {\
|
||||
Master.current_ticklimit = original_tick_limit;\
|
||||
}
|
||||
|
||||
// Boilerplate code for multi-step processors. See machines.dm for example use.
|
||||
#define INTERNAL_PROCESS_STEP(this_step, initial_step, proc_to_call, cost_var, next_step)\
|
||||
if(current_step == this_step || (initial_step && !resumed)) /* So we start at step 1 if not resumed.*/ {\
|
||||
timer = TICK_USAGE;\
|
||||
proc_to_call(resumed);\
|
||||
cost_var = MC_AVERAGE(cost_var, TICK_DELTA_TO_MS(TICK_USAGE - timer));\
|
||||
if(state != SS_RUNNING){\
|
||||
return;\
|
||||
}\
|
||||
resumed = 0;\
|
||||
current_step = next_step;\
|
||||
}
|
||||
|
||||
// Used to smooth out costs to try and avoid oscillation.
|
||||
#define MC_AVERAGE_FAST(average, current) (0.7 * (average) + 0.3 * (current))
|
||||
#define MC_AVERAGE(average, current) (0.8 * (average) + 0.2 * (current))
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
#define BACKGROUND_ENABLED 0 // The default value for all uses of set background. Set background can cause gradual lag and is recommended you only turn this on if necessary.
|
||||
// 1 will enable set background. 0 will disable set background.
|
||||
// 1 will enable set background. 0 will disable set background.
|
||||
|
||||
#define PRELOAD_RSC 1 /*set to:
|
||||
0 to allow using external resources or on-demand behaviour;
|
||||
1 to use the default behaviour (preload compiled in recourses, not player uploaded ones);
|
||||
2 for preloading absolutely everything;
|
||||
*/
|
||||
@@ -1,7 +1,9 @@
|
||||
#define TICK_LIMIT_RUNNING 80
|
||||
#define TICK_LIMIT_TO_RUN 78
|
||||
#define TICK_LIMIT_TO_RUN 70
|
||||
#define TICK_LIMIT_MC 70
|
||||
#define TICK_LIMIT_MC_INIT_DEFAULT 98
|
||||
|
||||
#define TICK_CHECK ( world.tick_usage > Master.current_ticklimit )
|
||||
#define TICK_CHECK ( TICK_USAGE > Master.current_ticklimit )
|
||||
#define CHECK_TICK if TICK_CHECK stoplag()
|
||||
|
||||
#define TICK_USAGE world.tick_usage
|
||||
@@ -105,3 +105,37 @@ var/list/restricted_camera_networks = list(NETWORK_ERT,NETWORK_MERCENARY,"Secret
|
||||
#define ATMOS_DEFAULT_VOLUME_FILTER 200 // L.
|
||||
#define ATMOS_DEFAULT_VOLUME_MIXER 200 // L.
|
||||
#define ATMOS_DEFAULT_VOLUME_PIPE 70 // L.
|
||||
|
||||
// Fancy-pants START/STOP_PROCESSING() macros that lets us custom define what the list is.
|
||||
#define START_PROCESSING_IN_LIST(DATUM, LIST) \
|
||||
if (DATUM.is_processing) {\
|
||||
if(DATUM.is_processing != #LIST)\
|
||||
{\
|
||||
crash_with("Failed to start processing. [log_info_line(DATUM)] is already being processed by [DATUM.is_processing] but queue attempt occured on [#LIST]."); \
|
||||
}\
|
||||
} else {\
|
||||
DATUM.is_processing = #LIST;\
|
||||
LIST += DATUM;\
|
||||
}
|
||||
|
||||
#define STOP_PROCESSING_IN_LIST(DATUM, LIST) \
|
||||
if(DATUM.is_processing) {\
|
||||
if(LIST.Remove(DATUM)) {\
|
||||
DATUM.is_processing = null;\
|
||||
} else {\
|
||||
crash_with("Failed to stop processing. [log_info_line(DATUM)] is being processed by [is_processing] and not found in SSmachines.[#LIST]"); \
|
||||
}\
|
||||
}
|
||||
|
||||
// Note - I would prefer these be defined machines.dm, but some are used prior in file order. ~Leshana
|
||||
#define START_MACHINE_PROCESSING(Datum) START_PROCESSING_IN_LIST(Datum, global.machines)
|
||||
#define STOP_MACHINE_PROCESSING(Datum) STOP_PROCESSING_IN_LIST(Datum, global.machines)
|
||||
|
||||
#define START_PROCESSING_PIPENET(Datum) START_PROCESSING_IN_LIST(Datum, global.pipe_networks)
|
||||
#define STOP_PROCESSING_PIPENET(Datum) STOP_PROCESSING_IN_LIST(Datum, global.pipe_networks)
|
||||
|
||||
#define START_PROCESSING_POWERNET(Datum) START_PROCESSING_IN_LIST(Datum, global.powernets)
|
||||
#define STOP_PROCESSING_POWERNET(Datum) STOP_PROCESSING_IN_LIST(Datum, global.powernets)
|
||||
|
||||
#define START_PROCESSING_POWER_OBJECT(Datum) START_PROCESSING_IN_LIST(Datum, global.processing_power_items)
|
||||
#define STOP_PROCESSING_POWER_OBJECT(Datum) STOP_PROCESSING_IN_LIST(Datum, global.processing_power_items)
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
//percent_of_tick_used * (ticklag * 100(to convert to ms)) / 100(percent ratio)
|
||||
//collapsed to percent_of_tick_used * tick_lag
|
||||
#define TICK_DELTA_TO_MS(percent_of_tick_used) ((percent_of_tick_used) * world.tick_lag)
|
||||
#define TICK_USAGE_TO_MS(starting_tickusage) (TICK_DELTA_TO_MS(world.tick_usage-starting_tickusage))
|
||||
#define TICK_USAGE_TO_MS(starting_tickusage) (TICK_DELTA_TO_MS(TICK_USAGE-starting_tickusage))
|
||||
|
||||
//time of day but automatically adjusts to the server going into the next day within the same round.
|
||||
//for when you need a reliable time number that doesn't depend on byond time.
|
||||
#define REALTIMEOFDAY (world.timeofday + (MIDNIGHT_ROLLOVER * MIDNIGHT_ROLLOVER_CHECK))
|
||||
#define MIDNIGHT_ROLLOVER_CHECK ( rollovercheck_last_timeofday != world.timeofday ? update_midnight_rollover() : midnight_rollovers )
|
||||
|
||||
#define CEILING(x, y) ( -round(-(x) / (y)) * (y) )
|
||||
// round() acts like floor(x, 1) by default but can't handle other values
|
||||
#define FLOOR(x, y) ( round((x) / (y)) * (y) )
|
||||
|
||||
@@ -227,4 +227,21 @@
|
||||
#define MAP_MINZ 3
|
||||
#define MAP_MAXX 4
|
||||
#define MAP_MAXY 5
|
||||
#define MAP_MAXZ 6
|
||||
#define MAP_MAXZ 6
|
||||
// /atom/proc/use_check flags
|
||||
#define USE_ALLOW_NONLIVING 1
|
||||
#define USE_ALLOW_NON_ADV_TOOL_USR 2
|
||||
#define USE_ALLOW_DEAD 4
|
||||
#define USE_ALLOW_INCAPACITATED 8
|
||||
#define USE_ALLOW_NON_ADJACENT 16
|
||||
#define USE_FORCE_SRC_IN_USER 32
|
||||
#define USE_DISALLOW_SILICONS 64
|
||||
|
||||
#define USE_SUCCESS 0
|
||||
#define USE_FAIL_NON_ADJACENT 1
|
||||
#define USE_FAIL_NONLIVING 2
|
||||
#define USE_FAIL_NON_ADV_TOOL_USR 3
|
||||
#define USE_FAIL_DEAD 4
|
||||
#define USE_FAIL_INCAPACITATED 5
|
||||
#define USE_FAIL_NOT_IN_USER 6
|
||||
#define USE_FAIL_IS_SILICON 7
|
||||
@@ -15,4 +15,4 @@
|
||||
#define PROCESS_DEFAULT_DEFER_USAGE 90 // 90% of a tick
|
||||
|
||||
// Sleep check macro
|
||||
#define SCHECK if(world.tick_usage >= next_sleep_usage) defer()
|
||||
#define SCHECK if(TICK_USAGE >= next_sleep_usage) defer()
|
||||
|
||||
@@ -9,9 +9,14 @@
|
||||
//if TESTING is enabled, qdel will call this object's find_references() verb.
|
||||
//defines for the gc_destroyed var
|
||||
|
||||
#define GC_QUEUE_PREQUEUE 1
|
||||
#define GC_QUEUE_CHECK 2
|
||||
#define GC_QUEUE_HARDDELETE 3
|
||||
#define GC_QUEUE_COUNT 3 //increase this when adding more steps.
|
||||
|
||||
#define GC_QUEUED_FOR_QUEUING -1
|
||||
#define GC_QUEUED_FOR_HARD_DEL -2
|
||||
#define GC_CURRENTLY_BEING_QDELETED -3
|
||||
|
||||
#define QDELETED(X) (!X || X.gc_destroyed)
|
||||
#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
|
||||
#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
|
||||
@@ -12,4 +12,8 @@
|
||||
var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_GAME, RUNLEVEL_POSTGAME)
|
||||
#define RUNLEVEL_FLAG_TO_INDEX(flag) (log(2, flag) + 1) // Convert from the runlevel bitfield constants to index in runlevel_flags list
|
||||
|
||||
// Subsystem init_order, from highest priority to lowest priority
|
||||
// Subsystems shutdown in the reverse of the order they initialize in
|
||||
// The numbers just define the ordering, they are meaningless otherwise.
|
||||
#define INIT_ORDER_MACHINES 10
|
||||
#define INIT_ORDER_LIGHTING 0
|
||||
|
||||
@@ -182,7 +182,7 @@ Proc for attack log creation, because really why not
|
||||
var/starttime = world.time
|
||||
. = 1
|
||||
while (world.time < endtime)
|
||||
sleep(1)
|
||||
stoplag(1)
|
||||
if (progress)
|
||||
progbar.update(world.time - starttime)
|
||||
if(!user || !target)
|
||||
@@ -229,7 +229,7 @@ Proc for attack log creation, because really why not
|
||||
var/starttime = world.time
|
||||
. = 1
|
||||
while (world.time < endtime)
|
||||
sleep(1)
|
||||
stoplag(1)
|
||||
if (progress)
|
||||
progbar.update(world.time - starttime)
|
||||
|
||||
|
||||
@@ -13,8 +13,18 @@
|
||||
|
||||
// Sorts subsystems by init_order
|
||||
/proc/cmp_subsystem_init(datum/controller/subsystem/a, datum/controller/subsystem/b)
|
||||
return b.init_order - a.init_order
|
||||
return initial(b.init_order) - initial(a.init_order) //uses initial() so it can be used on types
|
||||
|
||||
// Sorts subsystems by priority
|
||||
/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b)
|
||||
return a.priority - b.priority
|
||||
|
||||
// Sorts qdel statistics recorsd by time and count
|
||||
/proc/cmp_qdel_item_time(datum/qdel_item/A, datum/qdel_item/B)
|
||||
. = B.hard_delete_time - A.hard_delete_time
|
||||
if (!.)
|
||||
. = B.destroy_time - A.destroy_time
|
||||
if (!.)
|
||||
. = B.failures - A.failures
|
||||
if (!.)
|
||||
. = B.qdels - A.qdels
|
||||
|
||||
@@ -11,7 +11,13 @@
|
||||
#define DAYS *864000
|
||||
|
||||
#define TimeOfGame (get_game_time())
|
||||
#define TimeOfTick (world.tick_usage*0.01*world.tick_lag)
|
||||
#define TimeOfTick (TICK_USAGE*0.01*world.tick_lag)
|
||||
|
||||
#define TICK *world.tick_lag
|
||||
#define TICKS *world.tick_lag
|
||||
|
||||
#define DS2TICKS(DS) (DS/world.tick_lag) // Convert deciseconds to ticks
|
||||
#define TICKS2DS(T) (T TICKS) // Convert ticks to deciseconds
|
||||
|
||||
/proc/get_game_time()
|
||||
var/global/time_offset = 0
|
||||
@@ -19,7 +25,7 @@
|
||||
var/global/last_usage = 0
|
||||
|
||||
var/wtime = world.time
|
||||
var/wusage = world.tick_usage * 0.01
|
||||
var/wusage = TICK_USAGE * 0.01
|
||||
|
||||
if(last_time < wtime && last_usage > 1)
|
||||
time_offset += last_usage - 1
|
||||
@@ -111,13 +117,21 @@ var/round_start_time = 0
|
||||
|
||||
//Increases delay as the server gets more overloaded,
|
||||
//as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful
|
||||
#define DELTA_CALC max(((max(world.tick_usage, world.cpu) / 100) * max(Master.sleep_delta,1)), 1)
|
||||
#define DELTA_CALC max(((max(TICK_USAGE, world.cpu) / 100) * max(Master.sleep_delta-1,1)), 1)
|
||||
|
||||
/proc/stoplag()
|
||||
//returns the number of ticks slept
|
||||
/proc/stoplag(initial_delay)
|
||||
if (!Master || !(Master.current_runlevel & RUNLEVELS_DEFAULT))
|
||||
sleep(world.tick_lag)
|
||||
return 1
|
||||
if (!initial_delay)
|
||||
initial_delay = world.tick_lag
|
||||
. = 0
|
||||
var/i = 1
|
||||
var/i = DS2TICKS(initial_delay)
|
||||
do
|
||||
. += round(i*DELTA_CALC)
|
||||
. += CEILING(i*DELTA_CALC, 1)
|
||||
sleep(i*world.tick_lag*DELTA_CALC)
|
||||
i *= 2
|
||||
while (world.tick_usage > min(TICK_LIMIT_TO_RUN, Master.current_ticklimit))
|
||||
while (TICK_USAGE > min(TICK_LIMIT_TO_RUN, Master.current_ticklimit))
|
||||
|
||||
#undef DELTA_CALC
|
||||
@@ -1361,3 +1361,48 @@ var/mob/dview/dview_mob = new
|
||||
. += T.contents
|
||||
if(areas)
|
||||
. |= T.loc
|
||||
|
||||
#define NOT_FLAG(flag) (!(flag & use_flags))
|
||||
#define HAS_FLAG(flag) (flag & use_flags)
|
||||
|
||||
// Checks if user can use this object. Set use_flags to customize what checks are done.
|
||||
// Returns 0 if they can use it, a value representing why they can't if not.
|
||||
// Flags are in `code/__defines/misc.dm`
|
||||
/atom/proc/use_check(mob/user, use_flags = 0, show_messages = FALSE)
|
||||
. = 0
|
||||
if (NOT_FLAG(USE_ALLOW_NONLIVING) && !isliving(user))
|
||||
// No message for ghosts.
|
||||
return USE_FAIL_NONLIVING
|
||||
|
||||
if (NOT_FLAG(USE_ALLOW_NON_ADJACENT) && !Adjacent(user))
|
||||
if (show_messages)
|
||||
to_chat(user, span("notice","You're too far away from [src] to do that."))
|
||||
return USE_FAIL_NON_ADJACENT
|
||||
|
||||
if (NOT_FLAG(USE_ALLOW_DEAD) && user.stat == DEAD)
|
||||
if (show_messages)
|
||||
to_chat(user, span("notice","You can't do that when you're dead."))
|
||||
return USE_FAIL_DEAD
|
||||
|
||||
if (NOT_FLAG(USE_ALLOW_INCAPACITATED) && (user.incapacitated()))
|
||||
if (show_messages)
|
||||
to_chat(user, span("notice","You cannot do that in your current state."))
|
||||
return USE_FAIL_INCAPACITATED
|
||||
|
||||
if (NOT_FLAG(USE_ALLOW_NON_ADV_TOOL_USR) && !user.IsAdvancedToolUser())
|
||||
if (show_messages)
|
||||
to_chat(user, span("notice","You don't know how to operate [src]."))
|
||||
return USE_FAIL_NON_ADV_TOOL_USR
|
||||
|
||||
if (HAS_FLAG(USE_DISALLOW_SILICONS) && issilicon(user))
|
||||
if (show_messages)
|
||||
to_chat(user, span("notice","You need hands for that."))
|
||||
return USE_FAIL_IS_SILICON
|
||||
|
||||
if (HAS_FLAG(USE_FORCE_SRC_IN_USER) && !(src in user))
|
||||
if (show_messages)
|
||||
to_chat(user, span("notice","You need to be holding [src] to do that."))
|
||||
return USE_FAIL_NOT_IN_USER
|
||||
|
||||
#undef NOT_FLAG
|
||||
#undef HAS_FLAG
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#define CLAMP01(x) (Clamp(x, 0, 1))
|
||||
|
||||
#define span(class, text) ("<span class='[class]'>[text]</span>")
|
||||
|
||||
#define get_turf(A) get_step(A,0)
|
||||
|
||||
#define isAI(A) istype(A, /mob/living/silicon/ai)
|
||||
@@ -87,4 +89,4 @@
|
||||
// Null-safe L.Cut()
|
||||
#define LAZYCLEARLIST(L) if(L) L.Cut()
|
||||
// Reads L or an empty list if L is not a list. Note: Does NOT assign, L may be an expression.
|
||||
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
|
||||
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
|
||||
@@ -124,7 +124,7 @@
|
||||
cpu_defer_count = 0
|
||||
|
||||
// Prepare usage tracking (defer() updates these)
|
||||
tick_usage_start = world.tick_usage
|
||||
tick_usage_start = TICK_USAGE
|
||||
tick_usage_accumulated = 0
|
||||
|
||||
running()
|
||||
@@ -142,7 +142,7 @@
|
||||
|
||||
/datum/controller/process/proc/recordRunTime()
|
||||
// Convert from tick usage (100/tick) to seconds of CPU time used
|
||||
var/total_usage = (tick_usage_accumulated + (world.tick_usage - tick_usage_start)) / 1000 * world.tick_lag
|
||||
var/total_usage = (tick_usage_accumulated + (TICK_USAGE - tick_usage_start)) / 1000 * world.tick_lag
|
||||
|
||||
last_run_time = total_usage
|
||||
if(total_usage > highest_run_time)
|
||||
@@ -222,14 +222,14 @@
|
||||
handleHung()
|
||||
CRASH("Process [name] hung and was restarted.")
|
||||
|
||||
tick_usage_accumulated += (world.tick_usage - tick_usage_start)
|
||||
if(world.tick_usage < defer_usage)
|
||||
tick_usage_accumulated += (TICK_USAGE - tick_usage_start)
|
||||
if(TICK_USAGE < defer_usage)
|
||||
sleep(0)
|
||||
else
|
||||
sleep(world.tick_lag)
|
||||
cpu_defer_count++
|
||||
tick_usage_start = world.tick_usage
|
||||
next_sleep_usage = min(world.tick_usage + sleep_interval, defer_usage)
|
||||
tick_usage_start = TICK_USAGE
|
||||
next_sleep_usage = min(TICK_USAGE + sleep_interval, defer_usage)
|
||||
|
||||
/datum/controller/process/proc/update()
|
||||
// Clear delta
|
||||
|
||||
@@ -12,7 +12,7 @@ var/datum/controller/master/Master = new()
|
||||
name = "Master"
|
||||
|
||||
// Are we processing (higher values increase the processing delay by n ticks)
|
||||
var/processing = 1
|
||||
var/processing = TRUE
|
||||
// How many times have we ran
|
||||
var/iteration = 0
|
||||
|
||||
@@ -27,7 +27,7 @@ var/datum/controller/master/Master = new()
|
||||
var/init_time
|
||||
var/tickdrift = 0
|
||||
|
||||
var/sleep_delta
|
||||
var/sleep_delta = 1
|
||||
|
||||
var/make_runtime = 0
|
||||
|
||||
@@ -54,13 +54,17 @@ var/datum/controller/master/Master = new()
|
||||
|
||||
/datum/controller/master/New()
|
||||
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
|
||||
subsystems = list()
|
||||
var/list/_subsystems = list()
|
||||
subsystems = _subsystems
|
||||
if (Master != src)
|
||||
if (istype(Master))
|
||||
Recover()
|
||||
qdel(Master)
|
||||
else
|
||||
init_subtypes(/datum/controller/subsystem, subsystems)
|
||||
var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
|
||||
sortTim(subsytem_types, /proc/cmp_subsystem_init)
|
||||
for(var/I in subsytem_types)
|
||||
_subsystems += new I
|
||||
Master = src
|
||||
|
||||
/datum/controller/master/Destroy()
|
||||
@@ -73,7 +77,9 @@ var/datum/controller/master/Master = new()
|
||||
sortTim(subsystems, /proc/cmp_subsystem_init)
|
||||
reverseRange(subsystems)
|
||||
for(var/datum/controller/subsystem/ss in subsystems)
|
||||
log_world("Shutting down [ss.name] subsystem...")
|
||||
ss.Shutdown()
|
||||
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
|
||||
@@ -87,7 +93,7 @@ var/datum/controller/master/Master = new()
|
||||
var/delay = 50 * ++Master.restart_count
|
||||
Master.restart_timeout = world.time + delay
|
||||
Master.restart_clear = world.time + (delay * 2)
|
||||
Master.processing = 0 //stop ticking this one
|
||||
Master.processing = FALSE //stop ticking this one
|
||||
try
|
||||
new/datum/controller/master()
|
||||
catch
|
||||
@@ -114,7 +120,8 @@ var/datum/controller/master/Master = new()
|
||||
var/FireHim = FALSE
|
||||
if(istype(BadBoy))
|
||||
msg = null
|
||||
switch(++BadBoy.failure_strikes)
|
||||
LAZYINITLIST(BadBoy.failure_strikes)
|
||||
switch(++BadBoy.failure_strikes[BadBoy.type])
|
||||
if(2)
|
||||
msg = "The [BadBoy.name] subsystem was the last to fire for 2 controller restarts. It will be recovered now and disabled if it happens again."
|
||||
FireHim = TRUE
|
||||
@@ -262,35 +269,45 @@ var/datum/controller/master/Master = new()
|
||||
|
||||
iteration = 1
|
||||
var/error_level = 0
|
||||
var/sleep_delta = 0
|
||||
var/sleep_delta = 1
|
||||
var/list/subsystems_to_check
|
||||
//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
|
||||
if (processing <= 0)
|
||||
current_ticklimit = TICK_LIMIT_RUNNING
|
||||
sleep(10)
|
||||
continue
|
||||
|
||||
//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, so longer sleeps are more likely to run first
|
||||
if (world.tick_usage > TICK_LIMIT_MC)
|
||||
sleep_delta += 2
|
||||
//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)
|
||||
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
|
||||
sleep(world.tick_lag * (processing + sleep_delta))
|
||||
sleep(world.tick_lag * (processing * sleep_delta))
|
||||
continue
|
||||
|
||||
sleep_delta = MC_AVERAGE_FAST(sleep_delta, 0)
|
||||
if (last_run + (world.tick_lag * processing) > world.time)
|
||||
sleep_delta += 1
|
||||
if (world.tick_usage > (TICK_LIMIT_MC*0.5))
|
||||
//Byond resumed us late. assume it might have to do the same next tick
|
||||
if (last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time)
|
||||
sleep_delta += 1
|
||||
|
||||
sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta
|
||||
|
||||
if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) //we ran 3/4 of the way into the tick
|
||||
sleep_delta += 1
|
||||
|
||||
//debug
|
||||
if (make_runtime)
|
||||
var/datum/controller/subsystem/SS
|
||||
SS.can_fire = 0
|
||||
|
||||
if (!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick+(Failsafe.processing_interval*5)) < world.time))
|
||||
new/datum/controller/failsafe() // (re)Start the failsafe.
|
||||
|
||||
//now do the actual stuff
|
||||
if (!queue_head || !(iteration % 3))
|
||||
var/checking_runlevel = current_runlevel
|
||||
if(cached_runlevel != checking_runlevel)
|
||||
@@ -307,6 +324,7 @@ var/datum/controller/master/Master = new()
|
||||
subsystems_to_check = current_runlevel_subsystems
|
||||
else
|
||||
subsystems_to_check = tickersubsystems
|
||||
|
||||
if (CheckQueue(subsystems_to_check) <= 0)
|
||||
if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems))
|
||||
log_world("MC: SoftReset() failed, crashing")
|
||||
@@ -337,8 +355,10 @@ var/datum/controller/master/Master = new()
|
||||
iteration++
|
||||
last_run = world.time
|
||||
src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta)
|
||||
current_ticklimit = TICK_LIMIT_RUNNING - (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc.
|
||||
sleep(world.tick_lag * (processing + sleep_delta))
|
||||
current_ticklimit = TICK_LIMIT_RUNNING
|
||||
if (processing * sleep_delta <= world.tick_lag)
|
||||
current_ticklimit -= (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc if we plan on running next tick
|
||||
sleep(world.tick_lag * (processing * sleep_delta))
|
||||
|
||||
|
||||
|
||||
@@ -389,13 +409,13 @@ var/datum/controller/master/Master = new()
|
||||
|
||||
//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
|
||||
while (ran && queue_head && world.tick_usage < TICK_LIMIT_MC)
|
||||
while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC)
|
||||
ran = FALSE
|
||||
bg_calc = FALSE
|
||||
current_tick_budget = queue_priority_count
|
||||
queue_node = queue_head
|
||||
while (queue_node)
|
||||
if (ran && world.tick_usage > TICK_LIMIT_RUNNING)
|
||||
if (ran && TICK_USAGE > TICK_LIMIT_RUNNING)
|
||||
break
|
||||
|
||||
queue_node_flags = queue_node.flags
|
||||
@@ -407,7 +427,7 @@ var/datum/controller/master/Master = new()
|
||||
//(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 - world.tick_usage && ran_non_ticker)
|
||||
if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker)
|
||||
queue_node.queued_priority += queue_priority_count * 0.10
|
||||
queue_priority_count -= queue_node_priority
|
||||
queue_priority_count += queue_node.queued_priority
|
||||
@@ -419,19 +439,19 @@ var/datum/controller/master/Master = new()
|
||||
current_tick_budget = queue_priority_count_bg
|
||||
bg_calc = TRUE
|
||||
|
||||
tick_remaining = TICK_LIMIT_RUNNING - world.tick_usage
|
||||
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)
|
||||
else
|
||||
tick_precentage = tick_remaining
|
||||
|
||||
current_ticklimit = world.tick_usage + tick_precentage
|
||||
current_ticklimit = TICK_USAGE + tick_precentage
|
||||
|
||||
if (!(queue_node_flags & SS_TICKER))
|
||||
ran_non_ticker = TRUE
|
||||
ran = TRUE
|
||||
tick_usage = world.tick_usage
|
||||
tick_usage = TICK_USAGE
|
||||
queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING)
|
||||
last_type_processed = queue_node
|
||||
|
||||
@@ -441,7 +461,7 @@ var/datum/controller/master/Master = new()
|
||||
if (state == SS_RUNNING)
|
||||
state = SS_IDLE
|
||||
current_tick_budget -= queue_node_priority
|
||||
tick_usage = world.tick_usage - tick_usage
|
||||
tick_usage = TICK_USAGE - tick_usage
|
||||
|
||||
if (tick_usage < 0)
|
||||
tick_usage = 0
|
||||
|
||||
@@ -28,11 +28,10 @@
|
||||
var/datum/controller/subsystem/queue_next
|
||||
var/datum/controller/subsystem/queue_prev
|
||||
|
||||
var/static/failure_strikes = 0 //How many times we suspect this subsystem has crashed the MC, 3 strikes and you're out!
|
||||
var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out!
|
||||
|
||||
//Do not override
|
||||
/datum/controller/subsystem/New()
|
||||
return
|
||||
///datum/controller/subsystem/New()
|
||||
|
||||
// Used to initialize the subsystem BEFORE the map has loaded
|
||||
// Called AFTER Recover if that is called
|
||||
@@ -66,7 +65,7 @@
|
||||
can_fire = 0
|
||||
flags |= SS_NO_FIRE
|
||||
Master.subsystems -= src
|
||||
|
||||
return ..()
|
||||
|
||||
//Queue it to run.
|
||||
// (we loop thru a linked list until we get to the end or find the right point)
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
SUBSYSTEM_DEF(garbage)
|
||||
name = "Garbage"
|
||||
priority = 15
|
||||
wait = 5
|
||||
wait = 2 SECONDS
|
||||
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
|
||||
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
|
||||
|
||||
var/collection_timeout = 3000// deciseconds to wait to let running procs finish before we just say fuck it and force del() the object
|
||||
var/list/collection_timeout = list(0, 2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
|
||||
|
||||
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
|
||||
@@ -17,27 +18,32 @@ SUBSYSTEM_DEF(garbage)
|
||||
var/highest_del_time = 0
|
||||
var/highest_del_tickusage = 0
|
||||
|
||||
var/list/queue = list() // list of refID's of things that should be garbage collected
|
||||
// refID's are associated with the time at which they time out and need to be manually del()
|
||||
// we do this so we aren't constantly locating them and preventing them from being gc'd
|
||||
var/list/pass_counts
|
||||
var/list/fail_counts
|
||||
|
||||
var/list/tobequeued = list() //We store the references of things to be added to the queue seperately so we can spread out GC overhead over a few ticks
|
||||
var/list/items = list() // Holds our qdel_item statistics datums
|
||||
|
||||
var/list/didntgc = list() // list of all types that have failed to GC associated with the number of times that's happened.
|
||||
// the types are stored as strings
|
||||
var/list/sleptDestroy = list() //Same as above but these are paths that slept during their Destroy call
|
||||
// List of Queues
|
||||
// Each queue is a list of refID's of things that should be garbage collected
|
||||
// refID's are associated with the time at which they time out and need to be manually del()
|
||||
// we do this so we aren't constantly locating them and preventing them from being gc'd
|
||||
var/list/queues
|
||||
|
||||
var/list/noqdelhint = list()// list of all types that do not return a QDEL_HINT
|
||||
// all types that did not respect qdel(A, force=TRUE) and returned one
|
||||
// of the immortality qdel hints
|
||||
var/list/noforcerespect = list()
|
||||
|
||||
#ifdef TESTING
|
||||
var/list/qdel_list = list() // list of all types that have been qdel()eted
|
||||
#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
|
||||
|
||||
/datum/controller/subsystem/garbage/stat_entry(msg)
|
||||
msg += "Q:[queue.len]|D:[delslasttick]|G:[gcedlasttick]|"
|
||||
var/list/counts = list()
|
||||
for (var/list/L in queues)
|
||||
counts += length(L)
|
||||
msg += "Q:[counts.Join(",")]|D:[delslasttick]|G:[gcedlasttick]|"
|
||||
msg += "GR:"
|
||||
if (!(delslasttick+gcedlasttick))
|
||||
msg += "n/a|"
|
||||
@@ -49,116 +55,179 @@ SUBSYSTEM_DEF(garbage)
|
||||
msg += "n/a|"
|
||||
else
|
||||
msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%"
|
||||
msg += " P:[pass_counts.Join(",")]"
|
||||
msg += "|F:[fail_counts.Join(",")]"
|
||||
..(msg)
|
||||
|
||||
/datum/controller/subsystem/garbage/Shutdown()
|
||||
//Adds the del() log to world.log in a format condensable by the runtime condenser found in tools
|
||||
if(didntgc.len || sleptDestroy.len)
|
||||
var/list/dellog = list()
|
||||
for(var/path in didntgc)
|
||||
dellog += "Path : [path] \n"
|
||||
dellog += "Failures : [didntgc[path]] \n"
|
||||
if(path in sleptDestroy)
|
||||
dellog += "Sleeps : [sleptDestroy[path]] \n"
|
||||
sleptDestroy -= path
|
||||
for(var/path in sleptDestroy)
|
||||
dellog += "Path : [path] \n"
|
||||
dellog += "Sleeps : [sleptDestroy[path]] \n"
|
||||
//Adds the del() log to the qdel log file
|
||||
var/list/dellog = list()
|
||||
|
||||
//sort by how long it's wasted hard deleting
|
||||
sortTim(items, cmp=/proc/cmp_qdel_item_time, associative = TRUE)
|
||||
for(var/path in items)
|
||||
var/datum/qdel_item/I = items[path]
|
||||
dellog += "Path: [path]"
|
||||
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 += "\tTime Spent Hard Deleting: [I.hard_delete_time]ms"
|
||||
if (I.slept_destroy)
|
||||
dellog += "\tSleeps: [I.slept_destroy]"
|
||||
if (I.no_respect_force)
|
||||
dellog += "\tIgnored force: [I.no_respect_force] times"
|
||||
if (I.no_hint)
|
||||
dellog += "\tNo hint: [I.no_hint] times"
|
||||
log_misc(dellog.Join())
|
||||
|
||||
/datum/controller/subsystem/garbage/fire()
|
||||
HandleToBeQueued()
|
||||
if(state == SS_RUNNING)
|
||||
HandleQueue()
|
||||
|
||||
//the fact that this resets its processing each fire (rather then resume where it left off) is intentional.
|
||||
var/queue = GC_QUEUE_PREQUEUE
|
||||
|
||||
while (state == SS_RUNNING)
|
||||
switch (queue)
|
||||
if (GC_QUEUE_PREQUEUE)
|
||||
HandlePreQueue()
|
||||
queue = GC_QUEUE_PREQUEUE+1
|
||||
if (GC_QUEUE_CHECK)
|
||||
HandleQueue(GC_QUEUE_CHECK)
|
||||
queue = GC_QUEUE_CHECK+1
|
||||
if (GC_QUEUE_HARDDELETE)
|
||||
HandleQueue(GC_QUEUE_HARDDELETE)
|
||||
break
|
||||
|
||||
if (state == SS_PAUSED) //make us wait again before the next run.
|
||||
state = SS_RUNNING
|
||||
state = SS_RUNNING
|
||||
|
||||
//If you see this proc high on the profile, what you are really seeing is the garbage collection/soft delete overhead in byond.
|
||||
//Don't attempt to optimize, not worth the effort.
|
||||
/datum/controller/subsystem/garbage/proc/HandleToBeQueued()
|
||||
var/list/tobequeued = src.tobequeued
|
||||
var/starttime = world.time
|
||||
var/starttimeofday = world.timeofday
|
||||
while(tobequeued.len && starttime == world.time && starttimeofday == world.timeofday)
|
||||
if (MC_TICK_CHECK)
|
||||
break
|
||||
var/ref = tobequeued[1]
|
||||
Queue(ref)
|
||||
tobequeued.Cut(1, 2)
|
||||
/datum/controller/subsystem/garbage/proc/HandlePreQueue()
|
||||
var/list/tobequeued = queues[GC_QUEUE_PREQUEUE]
|
||||
var/static/count = 0
|
||||
if (count)
|
||||
var/c = count
|
||||
count = 0 //so if we runtime on the Cut, we don't try again.
|
||||
tobequeued.Cut(1,c+1)
|
||||
|
||||
/datum/controller/subsystem/garbage/proc/HandleQueue()
|
||||
delslasttick = 0
|
||||
gcedlasttick = 0
|
||||
var/time_to_kill = world.time - collection_timeout // Anything qdel() but not GC'd BEFORE this time needs to be manually del()
|
||||
var/list/queue = src.queue
|
||||
var/starttime = world.time
|
||||
var/starttimeofday = world.timeofday
|
||||
while(queue.len && starttime == world.time && starttimeofday == world.timeofday)
|
||||
for (var/ref in tobequeued)
|
||||
count++
|
||||
Queue(ref, GC_QUEUE_PREQUEUE+1)
|
||||
if (MC_TICK_CHECK)
|
||||
break
|
||||
var/refID = queue[1]
|
||||
if (count)
|
||||
tobequeued.Cut(1,count+1)
|
||||
count = 0
|
||||
|
||||
/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK)
|
||||
if (level == GC_QUEUE_CHECK)
|
||||
delslasttick = 0
|
||||
gcedlasttick = 0
|
||||
var/cut_off_time = world.time - collection_timeout[level] //ignore entries newer then this
|
||||
var/list/queue = queues[level]
|
||||
var/static/lastlevel
|
||||
var/static/count = 0
|
||||
if (count) //runtime last run before we could do this.
|
||||
var/c = count
|
||||
count = 0 //so if we runtime on the Cut, we don't try again.
|
||||
var/list/lastqueue = queues[lastlevel]
|
||||
lastqueue.Cut(1, c+1)
|
||||
|
||||
lastlevel = level
|
||||
|
||||
for (var/refID in queue)
|
||||
if (!refID)
|
||||
queue.Cut(1, 2)
|
||||
count++
|
||||
if (MC_TICK_CHECK)
|
||||
break
|
||||
continue
|
||||
|
||||
var/GCd_at_time = queue[refID]
|
||||
if(GCd_at_time > time_to_kill)
|
||||
if(GCd_at_time > cut_off_time)
|
||||
break // Everything else is newer, skip them
|
||||
queue.Cut(1, 2)
|
||||
var/datum/A
|
||||
A = locate(refID)
|
||||
if (A && A.gc_destroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
|
||||
#ifdef GC_FAILURE_HARD_LOOKUP
|
||||
A.find_references()
|
||||
#endif
|
||||
count++
|
||||
|
||||
// Something's still referring to the qdel'd object. Kill it.
|
||||
var/type = A.type
|
||||
testing("GC: -- \ref[A] | [type] was unable to be GC'd and was deleted --")
|
||||
didntgc["[type]"]++
|
||||
|
||||
HardDelete(A)
|
||||
var/datum/D
|
||||
D = locate(refID)
|
||||
|
||||
++delslasttick
|
||||
++totaldels
|
||||
else
|
||||
if (!D || D.gc_destroyed != GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
|
||||
++gcedlasttick
|
||||
++totalgcs
|
||||
pass_counts[level]++
|
||||
if (MC_TICK_CHECK)
|
||||
break
|
||||
continue
|
||||
|
||||
/datum/controller/subsystem/garbage/proc/QueueForQueuing(datum/A)
|
||||
if (istype(A) && A.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
|
||||
tobequeued += A
|
||||
A.gc_destroyed = GC_QUEUED_FOR_QUEUING
|
||||
// Something's still referring to the qdel'd object.
|
||||
fail_counts[level]++
|
||||
switch (level)
|
||||
if (GC_QUEUE_CHECK)
|
||||
#ifdef GC_FAILURE_HARD_LOOKUP
|
||||
D.find_references()
|
||||
#endif
|
||||
var/type = D.type
|
||||
var/datum/qdel_item/I = items[type]
|
||||
testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
|
||||
I.failures++
|
||||
if (GC_QUEUE_HARDDELETE)
|
||||
HardDelete(D)
|
||||
if (MC_TICK_CHECK)
|
||||
break
|
||||
continue
|
||||
|
||||
/datum/controller/subsystem/garbage/proc/Queue(datum/A)
|
||||
if (isnull(A) || (!isnull(A.gc_destroyed) && A.gc_destroyed >= 0))
|
||||
Queue(D, level+1)
|
||||
|
||||
if (MC_TICK_CHECK)
|
||||
break
|
||||
if (count)
|
||||
queue.Cut(1,count+1)
|
||||
count = 0
|
||||
|
||||
/datum/controller/subsystem/garbage/proc/PreQueue(datum/D)
|
||||
if (D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
|
||||
queues[GC_QUEUE_PREQUEUE] += D
|
||||
D.gc_destroyed = GC_QUEUED_FOR_QUEUING
|
||||
|
||||
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
|
||||
if (isnull(D))
|
||||
return
|
||||
if (A.gc_destroyed == GC_QUEUED_FOR_HARD_DEL)
|
||||
HardDelete(A)
|
||||
if (D.gc_destroyed == GC_QUEUED_FOR_HARD_DEL)
|
||||
level = GC_QUEUE_HARDDELETE
|
||||
if (level > GC_QUEUE_COUNT)
|
||||
HardDelete(D)
|
||||
return
|
||||
var/gctime = world.time
|
||||
var/refid = "\ref[A]"
|
||||
|
||||
A.gc_destroyed = gctime
|
||||
var/refid = "\ref[D]"
|
||||
|
||||
D.gc_destroyed = gctime
|
||||
var/list/queue = queues[level]
|
||||
if (queue[refid])
|
||||
queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list.
|
||||
|
||||
queue[refid] = gctime
|
||||
|
||||
//this is purely to seperate things profile wise.
|
||||
/datum/controller/subsystem/garbage/proc/HardDelete(datum/A)
|
||||
//this is mainly to separate things profile wise.
|
||||
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
|
||||
var/time = world.timeofday
|
||||
var/tick = world.tick_usage
|
||||
var/tick = TICK_USAGE
|
||||
var/ticktime = world.time
|
||||
|
||||
var/type = A.type
|
||||
var/refID = "\ref[A]"
|
||||
|
||||
del(A)
|
||||
|
||||
tick = (world.tick_usage-tick+((world.time-ticktime)/world.tick_lag*100))
|
||||
++delslasttick
|
||||
++totaldels
|
||||
var/type = D.type
|
||||
var/refID = "\ref[D]"
|
||||
|
||||
del(D)
|
||||
|
||||
tick = (TICK_USAGE-tick+((world.time-ticktime)/world.tick_lag*100))
|
||||
|
||||
var/datum/qdel_item/I = items[type]
|
||||
|
||||
I.hard_deletes++
|
||||
I.hard_delete_time += TICK_DELTA_TO_MS(tick)
|
||||
|
||||
|
||||
if (tick > highest_del_tickusage)
|
||||
highest_del_tickusage = tick
|
||||
time = world.timeofday - time
|
||||
@@ -169,18 +238,33 @@ SUBSYSTEM_DEF(garbage)
|
||||
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).")
|
||||
postpone(time/5)
|
||||
|
||||
/datum/controller/subsystem/garbage/proc/HardQueue(datum/A)
|
||||
if (istype(A) && A.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
|
||||
tobequeued += A
|
||||
A.gc_destroyed = GC_QUEUED_FOR_HARD_DEL
|
||||
postpone(time)
|
||||
|
||||
/datum/controller/subsystem/garbage/proc/HardQueue(datum/D)
|
||||
if (D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
|
||||
queues[GC_QUEUE_PREQUEUE] += D
|
||||
D.gc_destroyed = GC_QUEUED_FOR_HARD_DEL
|
||||
|
||||
/datum/controller/subsystem/garbage/Recover()
|
||||
if (istype(SSgarbage.queue))
|
||||
queue |= SSgarbage.queue
|
||||
if (istype(SSgarbage.tobequeued))
|
||||
tobequeued |= SSgarbage.tobequeued
|
||||
if (istype(SSgarbage.queues))
|
||||
for (var/i in 1 to SSgarbage.queues.len)
|
||||
queues[i] |= SSgarbage.queues[i]
|
||||
|
||||
|
||||
/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
|
||||
|
||||
/datum/qdel_item/New(mytype)
|
||||
name = "[mytype]"
|
||||
|
||||
|
||||
// Should be treated as a replacement for the 'del' keyword.
|
||||
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
|
||||
@@ -188,20 +272,26 @@ SUBSYSTEM_DEF(garbage)
|
||||
if(!istype(D))
|
||||
del(D)
|
||||
return
|
||||
#ifdef TESTING
|
||||
SSgarbage.qdel_list += D.type
|
||||
#endif
|
||||
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))
|
||||
D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
|
||||
var/start_time = world.time
|
||||
var/start_tick = world.tick_usage
|
||||
var/hint = D.Destroy(force) // Let our friend know they're about to get fucked up.
|
||||
if(world.time != start_time)
|
||||
SSgarbage.sleptDestroy[D.type]++
|
||||
I.slept_destroy++
|
||||
else
|
||||
I.destroy_time += TICK_USAGE_TO_MS(start_tick)
|
||||
if(!D)
|
||||
return
|
||||
switch(hint)
|
||||
if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
|
||||
SSgarbage.QueueForQueuing(D)
|
||||
SSgarbage.PreQueue(D)
|
||||
if (QDEL_HINT_IWILLGC)
|
||||
D.gc_destroyed = world.time
|
||||
return
|
||||
@@ -211,44 +301,37 @@ SUBSYSTEM_DEF(garbage)
|
||||
return
|
||||
// Returning LETMELIVE after being told to force destroy
|
||||
// indicates the objects Destroy() does not respect force
|
||||
if(!SSgarbage.noforcerespect[D.type])
|
||||
SSgarbage.noforcerespect[D.type] = D.type
|
||||
#ifdef TESTING
|
||||
if(!I.no_respect_force)
|
||||
crash_with("[D.type] has been force deleted, but is \
|
||||
returning an immortal QDEL_HINT, indicating it does \
|
||||
not respect the force flag for qdel(). It has been \
|
||||
placed in the queue, further instances of this type \
|
||||
will also be queued.")
|
||||
SSgarbage.QueueForQueuing(D)
|
||||
#endif
|
||||
I.no_respect_force++
|
||||
|
||||
SSgarbage.PreQueue(D)
|
||||
if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete using a hard reference to save time from the locate()
|
||||
SSgarbage.HardQueue(D)
|
||||
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
|
||||
SSgarbage.HardDelete(D)
|
||||
if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
|
||||
SSgarbage.QueueForQueuing(D)
|
||||
SSgarbage.PreQueue(D)
|
||||
#ifdef TESTING
|
||||
D.find_references()
|
||||
#endif
|
||||
else
|
||||
if(!SSgarbage.noqdelhint[D.type])
|
||||
SSgarbage.noqdelhint[D.type] = D.type
|
||||
#ifdef TESTING
|
||||
if(!I.no_hint)
|
||||
crash_with("[D.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
|
||||
SSgarbage.QueueForQueuing(D)
|
||||
#endif
|
||||
I.no_hint++
|
||||
SSgarbage.PreQueue(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")
|
||||
|
||||
// Default implementation of clean-up code.
|
||||
// This should be overridden to remove all references pointing to the object being destroyed.
|
||||
// Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE.
|
||||
/datum/proc/Destroy(force=FALSE)
|
||||
tag = null
|
||||
nanomanager.close_uis(src)
|
||||
return QDEL_HINT_QUEUE
|
||||
|
||||
/datum/var/gc_destroyed //Time when this object was destroyed.
|
||||
|
||||
#ifdef TESTING
|
||||
/datum/var/running_find_references
|
||||
/datum/var/last_find_references = 0
|
||||
|
||||
/datum/verb/find_refs()
|
||||
set category = "Debug"
|
||||
@@ -283,9 +366,17 @@ SUBSYSTEM_DEF(garbage)
|
||||
|
||||
testing("Beginning search for references to a [type].")
|
||||
last_find_references = world.time
|
||||
find_references_in_globals()
|
||||
for(var/datum/thing in world)
|
||||
DoSearchVar(thing, "WorldRef: [thing]")
|
||||
|
||||
// DoSearchVar(GLOB) // If we ever implement GLOB this would be the place.
|
||||
for(var/datum/thing in world) //atoms (don't beleive it's lies)
|
||||
DoSearchVar(thing, "World -> [thing]")
|
||||
|
||||
for (var/datum/thing) //datums
|
||||
DoSearchVar(thing, "World -> [thing]")
|
||||
|
||||
for (var/client/thing) //clients
|
||||
DoSearchVar(thing, "World -> [thing]")
|
||||
|
||||
testing("Completed search for references to a [type].")
|
||||
if(usr && usr.client)
|
||||
usr.client.running_find_references = null
|
||||
@@ -295,16 +386,6 @@ SUBSYSTEM_DEF(garbage)
|
||||
SSgarbage.can_fire = 1
|
||||
SSgarbage.next_fire = world.time + world.tick_lag
|
||||
|
||||
/client/verb/purge_all_destroyed_objects()
|
||||
set category = "Debug"
|
||||
if(SSgarbage)
|
||||
while(SSgarbage.queue.len)
|
||||
var/datum/o = locate(SSgarbage.queue[1])
|
||||
if(istype(o) && o.gc_destroyed)
|
||||
del(o)
|
||||
SSgarbage.totaldels++
|
||||
SSgarbage.queue.Cut(1, 2)
|
||||
|
||||
/datum/verb/qdel_then_find_references()
|
||||
set category = "Debug"
|
||||
set name = "qdel() then Find References"
|
||||
@@ -315,61 +396,47 @@ SUBSYSTEM_DEF(garbage)
|
||||
if(!running_find_references)
|
||||
find_references(TRUE)
|
||||
|
||||
/client/verb/show_qdeleted()
|
||||
set category = "Debug"
|
||||
set name = "Show qdel() Log"
|
||||
set desc = "Render the qdel() log and display it"
|
||||
/datum/proc/DoSearchVar(X, Xname, recursive_limit = 64)
|
||||
if(usr && usr.client && !usr.client.running_find_references)
|
||||
return
|
||||
if (!recursive_limit)
|
||||
return
|
||||
|
||||
var/dat = "<B>List of things that have been qdel()eted this round</B><BR><BR>"
|
||||
|
||||
var/tmplist = list()
|
||||
for(var/elem in SSgarbage.qdel_list)
|
||||
if(!(elem in tmplist))
|
||||
tmplist[elem] = 0
|
||||
tmplist[elem]++
|
||||
|
||||
for(var/path in tmplist)
|
||||
dat += "[path] - [tmplist[path]] times<BR>"
|
||||
|
||||
usr << browse(dat, "window=qdeletedlog")
|
||||
|
||||
/datum/proc/DoSearchVar(X, Xname)
|
||||
if(usr && usr.client && !usr.client.running_find_references) return
|
||||
if(istype(X, /datum))
|
||||
var/datum/D = X
|
||||
if(D.last_find_references == last_find_references)
|
||||
return
|
||||
|
||||
D.last_find_references = last_find_references
|
||||
for(var/V in D.vars)
|
||||
for(var/varname in D.vars)
|
||||
var/variable = D.vars[varname]
|
||||
if(variable == src)
|
||||
testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
|
||||
else if(islist(variable))
|
||||
if(src in variable)
|
||||
testing("Found [src.type] \ref[src] in [D.type]'s [varname] list var. Global: [Xname]")
|
||||
#ifdef GC_FAILURE_HARD_LOOKUP
|
||||
for(var/I in variable)
|
||||
DoSearchVar(I, TRUE)
|
||||
else
|
||||
DoSearchVar(variable, "[Xname]: [varname]")
|
||||
#endif
|
||||
var/list/L = D.vars
|
||||
|
||||
for(var/varname in L)
|
||||
if (varname == "vars")
|
||||
continue
|
||||
var/variable = L[varname]
|
||||
|
||||
if(variable == src)
|
||||
testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
|
||||
|
||||
else if(islist(variable))
|
||||
DoSearchVar(variable, "[Xname] -> list", recursive_limit-1)
|
||||
|
||||
else if(islist(X))
|
||||
if(src in X)
|
||||
testing("Found [src.type] \ref[src] in list [Xname].")
|
||||
#ifdef GC_FAILURE_HARD_LOOKUP
|
||||
var/normal = IS_NORMAL_LIST(X)
|
||||
for(var/I in X)
|
||||
DoSearchVar(I, Xname + ": list")
|
||||
#else
|
||||
if (I == src)
|
||||
testing("Found [src.type] \ref[src] in list [Xname].")
|
||||
|
||||
else if (I && !isnum(I) && normal && X[I] == src)
|
||||
testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]")
|
||||
|
||||
else if (islist(I))
|
||||
DoSearchVar(I, "[Xname] -> list", recursive_limit-1)
|
||||
|
||||
#ifndef FIND_REF_NO_CHECK_TICK
|
||||
CHECK_TICK
|
||||
#endif
|
||||
|
||||
//if find_references isn't working for some datum
|
||||
//update this list using tools/GenerateGlobalVarAccess
|
||||
/datum/proc/find_references_in_globals()
|
||||
// TODO - Impement Global Variable Access
|
||||
// for(var/global_var in _all_globals)
|
||||
// DoSearchVar(readglobal(global_var), "Global: [global_var]")
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -50,27 +50,27 @@ SUBSYSTEM_DEF(lighting)
|
||||
stage = SSLIGHTING_STAGE_LIGHTS // Start with Step 1 of course
|
||||
|
||||
if(stage == SSLIGHTING_STAGE_LIGHTS)
|
||||
timer = world.tick_usage
|
||||
timer = TICK_USAGE
|
||||
internal_process_lights(resumed)
|
||||
cost_lights = MC_AVERAGE(cost_lights, TICK_DELTA_TO_MS(world.tick_usage - timer))
|
||||
cost_lights = MC_AVERAGE(cost_lights, TICK_DELTA_TO_MS(TICK_USAGE - timer))
|
||||
if(state != SS_RUNNING)
|
||||
return
|
||||
resumed = 0
|
||||
stage = SSLIGHTING_STAGE_CORNERS
|
||||
|
||||
if(stage == SSLIGHTING_STAGE_CORNERS)
|
||||
timer = world.tick_usage
|
||||
timer = TICK_USAGE
|
||||
internal_process_corners(resumed)
|
||||
cost_corners = MC_AVERAGE(cost_corners, TICK_DELTA_TO_MS(world.tick_usage - timer))
|
||||
cost_corners = MC_AVERAGE(cost_corners, TICK_DELTA_TO_MS(TICK_USAGE - timer))
|
||||
if(state != SS_RUNNING)
|
||||
return
|
||||
resumed = 0
|
||||
stage = SSLIGHTING_STAGE_OVERLAYS
|
||||
|
||||
if(stage == SSLIGHTING_STAGE_OVERLAYS)
|
||||
timer = world.tick_usage
|
||||
timer = TICK_USAGE
|
||||
internal_process_overlays(resumed)
|
||||
cost_overlays = MC_AVERAGE(cost_overlays, TICK_DELTA_TO_MS(world.tick_usage - timer))
|
||||
cost_overlays = MC_AVERAGE(cost_overlays, TICK_DELTA_TO_MS(TICK_USAGE - timer))
|
||||
if(state != SS_RUNNING)
|
||||
return
|
||||
resumed = 0
|
||||
@@ -163,4 +163,4 @@ SUBSYSTEM_DEF(lighting)
|
||||
#undef SSLIGHTING_STAGE_LIGHTS
|
||||
#undef SSLIGHTING_STAGE_CORNERS
|
||||
#undef SSLIGHTING_STAGE_OVERLAYS
|
||||
#undef SSLIGHTING_STAGE_STATS
|
||||
#undef SSLIGHTING_STAGE_STATS
|
||||
160
code/controllers/subsystems/machines.dm
Normal file
@@ -0,0 +1,160 @@
|
||||
#define SSMACHINES_PIPENETS 1
|
||||
#define SSMACHINES_MACHINERY 2
|
||||
#define SSMACHINES_POWERNETS 3
|
||||
#define SSMACHINES_POWER_OBJECTS 4
|
||||
|
||||
//
|
||||
// SSmachines subsystem - Processing machines, pipenets, and powernets!
|
||||
//
|
||||
// Implementation Plan:
|
||||
// PHASE 1 - Add subsystem using the existing global list vars
|
||||
// PHASE 2 - Move the global list vars into the subsystem.
|
||||
|
||||
SUBSYSTEM_DEF(machines)
|
||||
name = "Machines"
|
||||
priority = 100
|
||||
init_order = INIT_ORDER_MACHINES
|
||||
flags = SS_KEEP_TIMING
|
||||
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
|
||||
|
||||
var/current_step = SSMACHINES_PIPENETS
|
||||
|
||||
var/cost_pipenets = 0
|
||||
var/cost_machinery = 0
|
||||
var/cost_powernets = 0
|
||||
var/cost_power_objects = 0
|
||||
|
||||
// TODO - PHASE 2 - Switch these from globals to instance vars
|
||||
// var/list/pipenets = list()
|
||||
// var/list/machinery = list()
|
||||
// var/list/powernets = list()
|
||||
// var/list/power_objects = list()
|
||||
|
||||
var/list/current_run = list()
|
||||
|
||||
/datum/controller/subsystem/machines/Initialize(timeofday)
|
||||
SSmachines.makepowernets()
|
||||
// TODO - Move world-creation time setup of atmos machinery and pipenets to here
|
||||
fire()
|
||||
..()
|
||||
|
||||
/datum/controller/subsystem/machines/fire(resumed = 0)
|
||||
var/timer = TICK_USAGE
|
||||
|
||||
INTERNAL_PROCESS_STEP(SSMACHINES_PIPENETS,TRUE,process_pipenets,cost_pipenets,SSMACHINES_MACHINERY)
|
||||
INTERNAL_PROCESS_STEP(SSMACHINES_MACHINERY,FALSE,process_machinery,cost_machinery,SSMACHINES_POWERNETS)
|
||||
INTERNAL_PROCESS_STEP(SSMACHINES_POWERNETS,FALSE,process_powernets,cost_powernets,SSMACHINES_POWER_OBJECTS)
|
||||
INTERNAL_PROCESS_STEP(SSMACHINES_POWER_OBJECTS,FALSE,process_power_objects,cost_power_objects,SSMACHINES_PIPENETS)
|
||||
|
||||
// rebuild all power networks from scratch - only called at world creation or by the admin verb
|
||||
// The above is a lie. Turbolifts also call this proc.
|
||||
/datum/controller/subsystem/machines/proc/makepowernets()
|
||||
// TODO - check to not run while in the middle of a tick!
|
||||
for(var/datum/powernet/PN in powernets)
|
||||
qdel(PN)
|
||||
powernets.Cut()
|
||||
|
||||
for(var/obj/structure/cable/PC in cable_list)
|
||||
if(!PC.powernet)
|
||||
var/datum/powernet/NewPN = new()
|
||||
NewPN.add_cable(PC)
|
||||
propagate_network(PC,PC.powernet)
|
||||
|
||||
/datum/controller/subsystem/machines/stat_entry()
|
||||
var/msg = list()
|
||||
msg += "C:{"
|
||||
msg += "PI:[round(cost_pipenets,1)]|"
|
||||
msg += "MC:[round(cost_machinery,1)]|"
|
||||
msg += "PN:[round(cost_powernets,1)]|"
|
||||
msg += "PO:[round(cost_power_objects,1)]"
|
||||
msg += "} "
|
||||
msg += "PI:[global.pipe_networks.len]|"
|
||||
msg += "MC:[global.machines.len]|"
|
||||
msg += "PN:[global.powernets.len]|"
|
||||
msg += "PO:[global.processing_power_items.len]|"
|
||||
msg += "MC/MS:[round((cost ? global.machines.len/cost_machinery : 0),0.1)]"
|
||||
..(jointext(msg, null))
|
||||
|
||||
/datum/controller/subsystem/machines/proc/process_pipenets(resumed = 0)
|
||||
if (!resumed)
|
||||
src.current_run = global.pipe_networks.Copy()
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/current_run = src.current_run
|
||||
while(current_run.len)
|
||||
var/datum/pipe_network/PN = current_run[current_run.len]
|
||||
current_run.len--
|
||||
if(istype(PN) && !QDELETED(PN))
|
||||
PN.process(wait)
|
||||
else
|
||||
global.pipe_networks.Remove(PN)
|
||||
if(!QDELETED(PN))
|
||||
PN.is_processing = null
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
/datum/controller/subsystem/machines/proc/process_machinery(resumed = 0)
|
||||
if (!resumed)
|
||||
src.current_run = global.machines.Copy()
|
||||
|
||||
var/list/current_run = src.current_run
|
||||
while(current_run.len)
|
||||
var/obj/machinery/M = current_run[current_run.len]
|
||||
current_run.len--
|
||||
if(istype(M) && !QDELETED(M) && !(M.process(wait) == PROCESS_KILL))
|
||||
if(M.use_power)
|
||||
M.auto_use_power()
|
||||
else
|
||||
global.machines.Remove(M)
|
||||
if(!QDELETED(M))
|
||||
M.is_processing = null
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
/datum/controller/subsystem/machines/proc/process_powernets(resumed = 0)
|
||||
if (!resumed)
|
||||
src.current_run = global.powernets.Copy()
|
||||
|
||||
var/list/current_run = src.current_run
|
||||
while(current_run.len)
|
||||
var/datum/powernet/PN = current_run[current_run.len]
|
||||
current_run.len--
|
||||
if(istype(PN) && !QDELETED(PN))
|
||||
PN.reset(wait)
|
||||
else
|
||||
global.powernets.Remove(PN)
|
||||
if(!QDELETED(PN))
|
||||
PN.is_processing = null
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
// Actually only processes power DRAIN objects.
|
||||
// Currently only used by powersinks. These items get priority processed before machinery
|
||||
/datum/controller/subsystem/machines/proc/process_power_objects(resumed = 0)
|
||||
if (!resumed)
|
||||
src.current_run = global.processing_power_items.Copy()
|
||||
|
||||
var/list/current_run = src.current_run
|
||||
while(current_run.len)
|
||||
var/obj/item/I = current_run[current_run.len]
|
||||
current_run.len--
|
||||
if(!I.pwr_drain(wait)) // 0 = Process Kill, remove from processing list.
|
||||
global.processing_power_items.Remove(I)
|
||||
I.is_processing = null
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
/datum/controller/subsystem/machines/Recover()
|
||||
// TODO - PHASE 2
|
||||
// if (istype(SSmachines.pipenets))
|
||||
// pipenets = SSmachines.pipenets
|
||||
// if (istype(SSmachines.machinery))
|
||||
// machinery = SSmachines.machinery
|
||||
// if (istype(SSmachines.powernets))
|
||||
// powernets = SSmachines.powernets
|
||||
// if (istype(SSmachines.power_objects))
|
||||
// power_objects = SSmachines.power_objects
|
||||
|
||||
#undef SSMACHINES_PIPENETS
|
||||
#undef SSMACHINES_MACHINERY
|
||||
#undef SSMACHINES_POWER
|
||||
#undef SSMACHINES_POWER_OBJECTS
|
||||
@@ -178,21 +178,21 @@
|
||||
/////// Shotgun
|
||||
|
||||
/datum/category_item/autolathe/arms/shotgun_clip_beanbag
|
||||
name = "4-round 12g shell clip (beanbag)"
|
||||
name = "2-round 12g speedloader (beanbag)"
|
||||
path =/obj/item/ammo_magazine/clip/c12g/beanbag
|
||||
|
||||
/datum/category_item/autolathe/arms/shotgun_clip_slug
|
||||
name = "4-round 12g shell clip (slug)"
|
||||
name = "2-round 12g speedloader (slug)"
|
||||
path =/obj/item/ammo_magazine/clip/c12g
|
||||
hidden = 1
|
||||
|
||||
/datum/category_item/autolathe/arms/shotgun_clip_pellet
|
||||
name = "4-round 12g shell clip (buckshot)"
|
||||
name = "2-round 12g speedloader (buckshot)"
|
||||
path =/obj/item/ammo_magazine/clip/c12g/pellet
|
||||
hidden = 1
|
||||
|
||||
/datum/category_item/autolathe/arms/shotgun_clip_beanbag
|
||||
name = "4-round 12g shell clip (beanbag)"
|
||||
name = "2-round 12g speedloader (beanbag)"
|
||||
path =/obj/item/ammo_magazine/clip/c12g/beanbag
|
||||
|
||||
/* Commented out until autolathe stuff is decided/fixed. Will probably remove these entirely. -Spades
|
||||
|
||||
23
code/datums/datum.dm
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// datum defines!
|
||||
// Note: Adding vars to /datum adds a var to EVERYTHING! Don't go overboard.
|
||||
//
|
||||
|
||||
/datum
|
||||
var/gc_destroyed //Time when this object was destroyed.
|
||||
var/weakref/weakref // Holder of weakref instance pointing to this datum
|
||||
var/is_processing = FALSE // If this datum is in an MC processing list, this will be set to its name.
|
||||
|
||||
#ifdef TESTING
|
||||
var/tmp/running_find_references
|
||||
var/tmp/last_find_references = 0
|
||||
#endif
|
||||
|
||||
// Default implementation of clean-up code.
|
||||
// This should be overridden to remove all references pointing to the object being destroyed.
|
||||
// Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE.
|
||||
/datum/proc/Destroy(force=FALSE)
|
||||
weakref = null // Clear this reference to ensure it's kept for as brief duration as possible.
|
||||
tag = null
|
||||
nanomanager.close_uis(src)
|
||||
return QDEL_HINT_QUEUE
|
||||
@@ -1,10 +1,3 @@
|
||||
/datum
|
||||
var/weakref/weakref
|
||||
|
||||
/datum/Destroy()
|
||||
weakref = null // Clear this reference to ensure it's kept for as brief duration as possible.
|
||||
. = ..()
|
||||
|
||||
//obtain a weak reference to a datum
|
||||
/proc/weakref(datum/D)
|
||||
if(!istype(D))
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
///////////////////////////////
|
||||
|
||||
/proc/pick_meteor_start(var/startSide = pick(cardinal))
|
||||
var/startLevel = pick(using_map.station_levels)
|
||||
var/startLevel = pick(using_map.station_levels - using_map.sealed_levels)
|
||||
var/pickedstart = spaceDebrisStartLoc(startSide, startLevel)
|
||||
|
||||
return list(startLevel, pickedstart)
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
welder.setWelding(1)
|
||||
|
||||
/obj/item/weapon/spell/flame_tongue/Destroy()
|
||||
qdel(welder)
|
||||
welder = null
|
||||
qdel_null(welder)
|
||||
return ..()
|
||||
|
||||
/obj/item/weapon/weldingtool/spell
|
||||
|
||||
@@ -62,8 +62,8 @@
|
||||
illusion.emote(what_to_emote)
|
||||
|
||||
/obj/item/weapon/spell/illusion/Destroy()
|
||||
if(illusion)
|
||||
qdel(illusion)
|
||||
qdel_null(illusion)
|
||||
copied = null
|
||||
return ..()
|
||||
|
||||
// Makes a tiny overlay of the thing the player has copied, so they can easily tell what they currently have.
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
for(var/atom/movable/AM in contents) //Eject everything out.
|
||||
AM.forceMove(get_turf(src))
|
||||
processing_objects -= src
|
||||
..()
|
||||
return ..()
|
||||
|
||||
/obj/effect/phase_shift/process()
|
||||
for(var/mob/living/L in contents)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
/obj/item/weapon/spell/radiance/Destroy()
|
||||
processing_objects -= src
|
||||
log_and_message_admins("has stopped maintaining [src].")
|
||||
..()
|
||||
return ..()
|
||||
|
||||
/obj/item/weapon/spell/radiance/process()
|
||||
var/turf/T = get_turf(src)
|
||||
|
||||
@@ -203,12 +203,7 @@ var/global/list/engineering_networks = list(
|
||||
/obj/machinery/camera/proc/upgradeMotion()
|
||||
assembly.upgrades.Add(new /obj/item/device/assembly/prox_sensor(assembly))
|
||||
setPowerUsage()
|
||||
if(!(src in machines))
|
||||
if(!machinery_sort_required && ticker)
|
||||
dd_insertObjectList(machines, src)
|
||||
else
|
||||
machines += src
|
||||
machinery_sort_required = 1
|
||||
START_MACHINE_PROCESSING(src)
|
||||
update_coverage()
|
||||
|
||||
/obj/machinery/camera/proc/setPowerUsage()
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
/obj/machinery/clonepod/process()
|
||||
|
||||
var/visible_message = 0
|
||||
for(var/obj/item/weapon/reagent_containers/food/snacks/meat in range(1, src))
|
||||
for(var/obj/item/weapon/reagent_containers/food/snacks/meat/meat in range(1, src))
|
||||
qdel(meat)
|
||||
biomass += 50
|
||||
visible_message = 1 // Prevent chatspam when multiple meat are near
|
||||
|
||||
@@ -118,16 +118,12 @@ Class Procs:
|
||||
..(l)
|
||||
if(d)
|
||||
set_dir(d)
|
||||
if(!machinery_sort_required && ticker)
|
||||
dd_insertObjectList(machines, src)
|
||||
else
|
||||
machines += src
|
||||
machinery_sort_required = 1
|
||||
START_MACHINE_PROCESSING(src)
|
||||
if(circuit)
|
||||
circuit = new circuit(src)
|
||||
|
||||
/obj/machinery/Destroy()
|
||||
machines -= src
|
||||
STOP_MACHINE_PROCESSING(src)
|
||||
if(component_parts)
|
||||
for(var/atom/A in component_parts)
|
||||
if(A.loc == src) // If the components are inside the machine, delete them.
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
singulo.target = src
|
||||
icon_state = "[icontype]1"
|
||||
active = 1
|
||||
machines |= src
|
||||
START_MACHINE_PROCESSING(src)
|
||||
if(user)
|
||||
user << "<span class='notice'>You activate the beacon.</span>"
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
M.adjustOxyLoss(round(dam_force/2))
|
||||
M.updatehealth()
|
||||
occupant_message("<span class='warning'>You squeeze [target] with [src.name]. Something cracks.</span>")
|
||||
playsound(src.loc, "fracture", 5, 1, -2) //CRACK
|
||||
chassis.visible_message("<span class='warning'>[chassis] squeezes [target].</span>")
|
||||
else
|
||||
step_away(M,chassis)
|
||||
@@ -408,7 +409,7 @@
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/gravcatapult
|
||||
name = "gravitational catapult"
|
||||
desc = "An exosuit mounted Gravitational Catapult."
|
||||
desc = "An exosuit mounted gravitational catapult."
|
||||
icon_state = "mecha_teleport"
|
||||
origin_tech = list(TECH_BLUESPACE = 2, TECH_MAGNET = 3)
|
||||
equip_cooldown = 10
|
||||
@@ -1041,7 +1042,7 @@
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/passenger
|
||||
name = "passenger compartment"
|
||||
desc = "A mountable passenger compartment for exo-suits. Rather cramped."
|
||||
desc = "A mountable passenger compartment for exosuits. Rather cramped."
|
||||
icon_state = "mecha_abooster_ccw"
|
||||
origin_tech = list(TECH_ENGINEERING = 1, TECH_BIO = 1)
|
||||
energy_drain = 10
|
||||
@@ -1071,9 +1072,9 @@
|
||||
log_message("[user] boarded.")
|
||||
occupant_message("[user] boarded.")
|
||||
else if(src.occupant != user)
|
||||
user << "<span class='warning'>[src.occupant] was faster. Try better next time, loser.</span>"
|
||||
to_chat(user, "<span class='warning'>[src.occupant] was faster. Try harder next time, loser.</span>")
|
||||
else
|
||||
user << "You stop entering the exosuit."
|
||||
to_chat(user, "You stop entering the exosuit.")
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/passenger/verb/eject()
|
||||
set name = "Eject"
|
||||
@@ -1083,7 +1084,7 @@
|
||||
|
||||
if(usr != occupant)
|
||||
return
|
||||
occupant << "You climb out from \the [src]."
|
||||
to_chat(occupant, "You climb out from \the [src].")
|
||||
go_out()
|
||||
occupant_message("[occupant] disembarked.")
|
||||
log_message("[occupant] disembarked.")
|
||||
@@ -1186,3 +1187,98 @@
|
||||
|
||||
#undef LOCKED
|
||||
#undef OCCUPIED
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack
|
||||
name = "ion jetpack"
|
||||
desc = "Using directed ion bursts and cunning solar wind reflection technique, this device enables controlled space flight."
|
||||
icon_state = "mecha_jetpack"
|
||||
equip_cooldown = 5
|
||||
energy_drain = 50
|
||||
var/wait = 0
|
||||
var/datum/effect/effect/system/ion_trail_follow/ion_trail
|
||||
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/can_attach(obj/mecha/M as obj)
|
||||
if(!(locate(src.type) in M.equipment) && !M.proc_res["dyndomove"])
|
||||
return ..()
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/detach()
|
||||
..()
|
||||
chassis.proc_res["dyndomove"] = null
|
||||
return
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/attach(obj/mecha/M as obj)
|
||||
..()
|
||||
if(!ion_trail)
|
||||
ion_trail = new
|
||||
ion_trail.set_up(chassis)
|
||||
return
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/proc/toggle()
|
||||
if(!chassis)
|
||||
return
|
||||
!equip_ready? turn_off() : turn_on()
|
||||
return equip_ready
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/proc/turn_on()
|
||||
set_ready_state(0)
|
||||
chassis.proc_res["dyndomove"] = src
|
||||
ion_trail.start()
|
||||
occupant_message("Activated")
|
||||
log_message("Activated")
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/proc/turn_off()
|
||||
set_ready_state(1)
|
||||
chassis.proc_res["dyndomove"] = null
|
||||
ion_trail.stop()
|
||||
occupant_message("Deactivated")
|
||||
log_message("Deactivated")
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/proc/dyndomove(direction)
|
||||
if(!action_checks())
|
||||
return chassis.dyndomove(direction)
|
||||
var/move_result = 0
|
||||
if(chassis.hasInternalDamage(MECHA_INT_CONTROL_LOST))
|
||||
move_result = step_rand(chassis)
|
||||
else if(chassis.dir!=direction)
|
||||
chassis.set_dir(direction)
|
||||
move_result = 1
|
||||
else
|
||||
move_result = step(chassis,direction)
|
||||
if(chassis.occupant)
|
||||
for(var/obj/effect/speech_bubble/B in range(1, chassis))
|
||||
if(B.parent == chassis.occupant)
|
||||
B.loc = chassis.loc
|
||||
if(move_result)
|
||||
wait = 1
|
||||
chassis.use_power(energy_drain)
|
||||
if(!chassis.pr_inertial_movement.active())
|
||||
chassis.pr_inertial_movement.start(list(chassis,direction))
|
||||
else
|
||||
chassis.pr_inertial_movement.set_process_args(list(chassis,direction))
|
||||
do_after_cooldown()
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/action_checks()
|
||||
if(equip_ready || wait)
|
||||
return 0
|
||||
if(energy_drain && !chassis.has_charge(energy_drain))
|
||||
return 0
|
||||
if(chassis.check_for_support())
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/get_equip_info()
|
||||
if(!chassis) return
|
||||
return "<span style=\"color:[equip_ready?"#0f0":"#f00"];\">*</span> [src.name] \[<a href=\"?src=\ref[src];toggle=1\">Toggle</a>\]"
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/Topic(href,href_list)
|
||||
..()
|
||||
if(href_list["toggle"])
|
||||
toggle()
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/do_after_cooldown()
|
||||
sleep(equip_cooldown)
|
||||
wait = 0
|
||||
return 1
|
||||
@@ -6,134 +6,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
//NEEDS SPRITE! (When this gets ticked in search for 'TODO MECHA JETPACK SPRITE MISSING' through code to uncomment the place where it's missing.)
|
||||
/obj/item/mecha_parts/mecha_equipment/jetpack
|
||||
name = "jetpack"
|
||||
desc = "Using directed ion bursts and cunning solar wind reflection technique, this device enables controlled space flight."
|
||||
icon_state = "mecha_equip"
|
||||
equip_cooldown = 5
|
||||
energy_drain = 50
|
||||
var/wait = 0
|
||||
var/datum/effect/effect/system/ion_trail_follow/ion_trail
|
||||
|
||||
|
||||
can_attach(obj/mecha/M as obj)
|
||||
if(!(locate(src.type) in M.equipment) && !M.proc_res["dyndomove"])
|
||||
return ..()
|
||||
|
||||
detach()
|
||||
..()
|
||||
chassis.proc_res["dyndomove"] = null
|
||||
return
|
||||
|
||||
attach(obj/mecha/M as obj)
|
||||
..()
|
||||
if(!ion_trail)
|
||||
ion_trail = new
|
||||
ion_trail.set_up(chassis)
|
||||
return
|
||||
|
||||
proc/toggle()
|
||||
if(!chassis)
|
||||
return
|
||||
!equip_ready? turn_off() : turn_on()
|
||||
return equip_ready
|
||||
|
||||
proc/turn_on()
|
||||
set_ready_state(0)
|
||||
chassis.proc_res["dyndomove"] = src
|
||||
ion_trail.start()
|
||||
occupant_message("Activated")
|
||||
log_message("Activated")
|
||||
|
||||
proc/turn_off()
|
||||
set_ready_state(1)
|
||||
chassis.proc_res["dyndomove"] = null
|
||||
ion_trail.stop()
|
||||
occupant_message("Deactivated")
|
||||
log_message("Deactivated")
|
||||
|
||||
proc/dyndomove(direction)
|
||||
if(!action_checks())
|
||||
return chassis.dyndomove(direction)
|
||||
var/move_result = 0
|
||||
if(chassis.hasInternalDamage(MECHA_INT_CONTROL_LOST))
|
||||
move_result = step_rand(chassis)
|
||||
else if(chassis.dir!=direction)
|
||||
chassis.set_dir(direction)
|
||||
move_result = 1
|
||||
else
|
||||
move_result = step(chassis,direction)
|
||||
if(chassis.occupant)
|
||||
for(var/obj/effect/speech_bubble/B in range(1, chassis))
|
||||
if(B.parent == chassis.occupant)
|
||||
B.loc = chassis.loc
|
||||
if(move_result)
|
||||
wait = 1
|
||||
chassis.use_power(energy_drain)
|
||||
if(!chassis.pr_inertial_movement.active())
|
||||
chassis.pr_inertial_movement.start(list(chassis,direction))
|
||||
else
|
||||
chassis.pr_inertial_movement.set_process_args(list(chassis,direction))
|
||||
do_after_cooldown()
|
||||
return 1
|
||||
return 0
|
||||
|
||||
action_checks()
|
||||
if(equip_ready || wait)
|
||||
return 0
|
||||
if(energy_drain && !chassis.has_charge(energy_drain))
|
||||
return 0
|
||||
if(chassis.check_for_support())
|
||||
return 0
|
||||
return 1
|
||||
|
||||
get_equip_info()
|
||||
if(!chassis) return
|
||||
return "<span style=\"color:[equip_ready?"#0f0":"#f00"];\">*</span> [src.name] \[<a href=\"?src=\ref[src];toggle=1\">Toggle</a>\]"
|
||||
|
||||
|
||||
Topic(href,href_list)
|
||||
..()
|
||||
if(href_list["toggle"])
|
||||
toggle()
|
||||
|
||||
do_after_cooldown()
|
||||
sleep(equip_cooldown)
|
||||
wait = 0
|
||||
return 1
|
||||
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/defence_shocker
|
||||
name = "exosuit defence shocker"
|
||||
desc = ""
|
||||
icon_state = "mecha_teleport"
|
||||
equip_cooldown = 10
|
||||
energy_drain = 100
|
||||
range = RANGED
|
||||
var/shock_damage = 15
|
||||
var/active
|
||||
|
||||
can_attach(obj/mecha/M as obj)
|
||||
if(..())
|
||||
if(!istype(M, /obj/mecha/combat/honker))
|
||||
if(!M.proc_res["dynattackby"] && !M.proc_res["dynattackhand"] && !M.proc_res["dynattackalien"])
|
||||
return 1
|
||||
return 0
|
||||
|
||||
attach(obj/mecha/M as obj)
|
||||
..()
|
||||
chassis.proc_res["dynattackby"] = src
|
||||
return
|
||||
|
||||
proc/dynattackby(obj/item/weapon/W as obj, mob/user as mob)
|
||||
if(!action_checks(user) || !active)
|
||||
return
|
||||
user.electrocute_act(shock_damage, src)
|
||||
return chassis.dynattackby(W,user)
|
||||
|
||||
|
||||
/*
|
||||
/obj/item/mecha_parts/mecha_equipment/book_stocker
|
||||
|
||||
|
||||
@@ -117,20 +117,18 @@
|
||||
projectile = /obj/item/projectile/beam/stun
|
||||
fire_sound = 'sound/weapons/Taser.ogg'
|
||||
|
||||
/* Commenting this out rather than removing it because it may be useful for reference.
|
||||
/*
|
||||
/obj/item/mecha_parts/mecha_equipment/weapon/honker
|
||||
name = "\improper HoNkER BlAsT 5000"
|
||||
name = "sound emission device"
|
||||
icon_state = "mecha_honker"
|
||||
energy_drain = 200
|
||||
energy_drain = 300
|
||||
equip_cooldown = 150
|
||||
range = MELEE|RANGED
|
||||
construction_time = 500
|
||||
construction_cost = list("metal"=20000,"bananium"=10000)
|
||||
origin_tech = list(TECH_MATERIAL = 2, TECH_COMBAT = 4, TECH_ILLEGAL = 1)
|
||||
|
||||
can_attach(obj/mecha/combat/honker/M as obj)
|
||||
if(!istype(M))
|
||||
return 0
|
||||
return ..()
|
||||
var/ear_safety = 0
|
||||
if(iscarbon(M))
|
||||
ear_safety = M.get_ear_protection()
|
||||
|
||||
action(target)
|
||||
if(!chassis)
|
||||
@@ -140,25 +138,22 @@
|
||||
if(!equip_ready)
|
||||
return 0
|
||||
|
||||
playsound(chassis, 'sound/items/AirHorn.ogg', 100, 1)
|
||||
chassis.occupant_message("<font color='red' size='5'>HONK</font>")
|
||||
playsound(chassis, 'sound/effects/bang.ogg', 30, 1, 30)
|
||||
chassis.occupant_message("<span class='warning'>You emit a high-pitched noise from the mech.</span>")
|
||||
for(var/mob/living/carbon/M in ohearers(6, chassis))
|
||||
if(istype(M, /mob/living/carbon/human))
|
||||
var/mob/living/carbon/human/H = M
|
||||
if(istype(H.l_ear, /obj/item/clothing/ears/earmuffs) || istype(H.r_ear, /obj/item/clothing/ears/earmuffs))
|
||||
continue
|
||||
M << "<font color='red' size='7'>HONK</font>"
|
||||
if(ear_safety > 0)
|
||||
return
|
||||
to_chat(M, "<span class='warning'>\Your ears feel like they're bleeding!</span>")
|
||||
playsound(M, 'sound/effects/bang.ogg', 70, 1, 30)
|
||||
M.sleeping = 0
|
||||
M.stuttering += 20
|
||||
M.ear_deaf += 30
|
||||
M.ear_damage += rand(5, 20)
|
||||
M.Weaken(3)
|
||||
if(prob(30))
|
||||
M.Stun(10)
|
||||
M.Paralyse(4)
|
||||
else
|
||||
M.make_jittery(500)
|
||||
M.Stun(5)
|
||||
chassis.use_power(energy_drain)
|
||||
log_message("Honked from [src.name]. HONK!")
|
||||
log_message("Used a sound emission device.")
|
||||
do_after_cooldown()
|
||||
return
|
||||
*/
|
||||
@@ -335,3 +330,35 @@
|
||||
projectile = /obj/item/projectile/bullet/incendiary/flamethrower
|
||||
|
||||
origin_tech = list(TECH_MATERIAL = 3, TECH_COMBAT = 3, TECH_PHORON = 3, TECH_ILLEGAL = 2)
|
||||
|
||||
//////////////
|
||||
//Defensive//
|
||||
//////////////
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/shocker
|
||||
name = "exosuit electrifier"
|
||||
desc = "A device to electrify the external portions of a mecha in order to increase its defensive capabilities."
|
||||
icon_state = "mecha_coil"
|
||||
equip_cooldown = 10
|
||||
energy_drain = 100
|
||||
range = RANGED
|
||||
origin_tech = list(TECH_COMBAT = 3, TECH_POWER = 6)
|
||||
var/shock_damage = 15
|
||||
var/active
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/shocker/can_attach(obj/mecha/M as obj)
|
||||
if(..())
|
||||
if(!M.proc_res["dynattackby"] && !M.proc_res["dynattackhand"] && !M.proc_res["dynattackalien"])
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/shocker/attach(obj/mecha/M as obj)
|
||||
..()
|
||||
chassis.proc_res["dynattackby"] = src
|
||||
return
|
||||
|
||||
/obj/item/mecha_parts/mecha_equipment/shocker/proc/dynattackby(obj/item/weapon/W, mob/living/user)
|
||||
if(!action_checks(user) || !active)
|
||||
return
|
||||
user.electrocute_act(shock_damage, src)
|
||||
return chassis.dynattackby(W,user)
|
||||
@@ -5,4 +5,23 @@
|
||||
icon = 'icons/obj/items.dmi'
|
||||
icon_state = "strangepresent"
|
||||
density = 1
|
||||
anchored = 0
|
||||
anchored = 0
|
||||
|
||||
// Shown really briefly when attacking with axes.
|
||||
/obj/effect/temporary_effect/cleave_attack
|
||||
name = "cleaving attack"
|
||||
desc = "Something swinging really wide."
|
||||
icon = 'icons/effects/96x96.dmi'
|
||||
icon_state = "cleave"
|
||||
layer = 6
|
||||
time_to_die = 6
|
||||
alpha = 140
|
||||
invisibility = 0
|
||||
mouse_opacity = 0
|
||||
new_light_range = 0
|
||||
new_light_power = 0
|
||||
pixel_x = -32
|
||||
pixel_y = -32
|
||||
|
||||
/obj/effect/temporary_effect/cleave_attack/initialize() // Makes the slash fade smoothly. When completely transparent it should qdel itself.
|
||||
animate(src, alpha = 0, time = time_to_die - 1)
|
||||
|
||||
@@ -104,7 +104,7 @@ proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impa
|
||||
sleep(8)
|
||||
|
||||
if(!powernet_rebuild_was_deferred_already && defer_powernet_rebuild)
|
||||
makepowernets()
|
||||
SSmachines.makepowernets()
|
||||
defer_powernet_rebuild = 0
|
||||
return 1
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
sparks.attach(loc)
|
||||
|
||||
/obj/item/weapon/antag_spawner/Destroy()
|
||||
qdel(sparks)
|
||||
qdel_null(sparks)
|
||||
return ..()
|
||||
|
||||
/obj/item/weapon/antag_spawner/proc/spawn_antag(client/C, turf/T)
|
||||
|
||||
238
code/game/objects/items/devices/communicator/UI.dm
Normal file
@@ -0,0 +1,238 @@
|
||||
// Proc: ui_interact()
|
||||
// Parameters: 4 (standard NanoUI arguments)
|
||||
// Description: Uses a bunch of for loops to turn lists into lists of lists, so they can be displayed in nanoUI, then displays various buttons to the user.
|
||||
/obj/item/device/communicator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/key_state = null)
|
||||
// this is the data which will be sent to the ui
|
||||
var/data[0] //General nanoUI information
|
||||
var/communicators[0] //List of communicators
|
||||
var/invites[0] //Communicators and ghosts we've invited to our communicator.
|
||||
var/requests[0] //Communicators and ghosts wanting to go in our communicator.
|
||||
var/voices[0] //Current /mob/living/voice s inside the device.
|
||||
var/connected_communicators[0] //Current communicators connected to the device.
|
||||
|
||||
var/im_contacts_ui[0] //List of communicators that have been messaged.
|
||||
var/im_list_ui[0] //List of messages.
|
||||
|
||||
var/weather[0]
|
||||
var/modules_ui[0] //Home screen info.
|
||||
|
||||
//First we add other 'local' communicators.
|
||||
for(var/obj/item/device/communicator/comm in known_devices)
|
||||
if(comm.network_visibility && comm.exonet)
|
||||
communicators[++communicators.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address)
|
||||
|
||||
//Now for ghosts who we pretend have communicators.
|
||||
for(var/mob/observer/dead/O in known_devices)
|
||||
if(O.client && O.client.prefs.communicator_visibility == 1 && O.exonet)
|
||||
communicators[++communicators.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
|
||||
|
||||
//Lists all the other communicators that we invited.
|
||||
for(var/obj/item/device/communicator/comm in voice_invites)
|
||||
if(comm.exonet)
|
||||
invites[++invites.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
|
||||
|
||||
//Ghosts we invited.
|
||||
for(var/mob/observer/dead/O in voice_invites)
|
||||
if(O.exonet && O.client)
|
||||
invites[++invites.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
|
||||
|
||||
//Communicators that want to talk to us.
|
||||
for(var/obj/item/device/communicator/comm in voice_requests)
|
||||
if(comm.exonet)
|
||||
requests[++requests.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
|
||||
|
||||
//Ghosts that want to talk to us.
|
||||
for(var/mob/observer/dead/O in voice_requests)
|
||||
if(O.exonet && O.client)
|
||||
requests[++requests.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
|
||||
|
||||
//Now for all the voice mobs inside the communicator.
|
||||
for(var/mob/living/voice/voice in contents)
|
||||
voices[++voices.len] = list("name" = sanitize("[voice.name]'s communicator"), "true_name" = sanitize(voice.name))
|
||||
|
||||
//Finally, all the communicators linked to this one.
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
connected_communicators[++connected_communicators.len] = list("name" = sanitize(comm.name), "true_name" = sanitize(comm.name), "ref" = "\ref[comm]")
|
||||
|
||||
//Devices that have been messaged or recieved messages from.
|
||||
for(var/obj/item/device/communicator/comm in im_contacts)
|
||||
if(comm.exonet)
|
||||
im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
|
||||
|
||||
for(var/mob/observer/dead/ghost in im_contacts)
|
||||
if(ghost.exonet)
|
||||
im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(ghost.name), "address" = ghost.exonet.address, "ref" = "\ref[ghost]")
|
||||
|
||||
//Actual messages.
|
||||
for(var/I in im_list)
|
||||
im_list_ui[++im_list_ui.len] = list("address" = I["address"], "to_address" = I["to_address"], "im" = I["im"])
|
||||
|
||||
//Weather reports.
|
||||
for(var/datum/planet/planet in planet_controller.planets)
|
||||
if(planet.weather_holder && planet.weather_holder.current_weather)
|
||||
weather[++weather.len] = list("Planet" = planet.name, "Weather" = planet.weather_holder.current_weather.name, "Temperature" = (round(planet.weather_holder.temperature*1000)/1000),\
|
||||
"High" = planet.weather_holder.current_weather.temp_high, "Low" = planet.weather_holder.current_weather.temp_low, "Wind" = "[planet.weather_holder.wind_speed]|[dir2text(planet.weather_holder.wind_dir)]")
|
||||
|
||||
//Modules for homescreen.
|
||||
for(var/list/R in modules)
|
||||
modules_ui[++modules_ui.len] = R
|
||||
|
||||
data["owner"] = owner ? owner : "Unset"
|
||||
data["occupation"] = occupation ? occupation : "Swipe ID to set."
|
||||
data["connectionStatus"] = get_connection_to_tcomms()
|
||||
data["visible"] = network_visibility
|
||||
data["address"] = exonet.address ? exonet.address : "Unallocated"
|
||||
data["targetAddress"] = target_address
|
||||
data["targetAddressName"] = target_address_name
|
||||
data["currentTab"] = selected_tab
|
||||
data["knownDevices"] = communicators
|
||||
data["invitesSent"] = invites
|
||||
data["requestsReceived"] = requests
|
||||
data["voice_mobs"] = voices
|
||||
data["communicating"] = connected_communicators
|
||||
data["video_comm"] = video_source ? "\ref[video_source.loc]" : null
|
||||
data["imContacts"] = im_contacts_ui
|
||||
data["imList"] = im_list_ui
|
||||
data["time"] = stationtime2text()
|
||||
data["ring"] = ringer
|
||||
data["homeScreen"] = modules_ui
|
||||
data["note"] = note // current notes
|
||||
data["weather"] = weather
|
||||
|
||||
// update the ui if it exists, returns null if no ui is passed/found
|
||||
ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
|
||||
if(!ui)
|
||||
// the ui does not exist, so we'll create a new() one
|
||||
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
|
||||
ui = new(user, src, ui_key, "communicator.tmpl", "Communicator", 475, 700, state = key_state)
|
||||
// when the ui is first opened this is the data it will use
|
||||
ui.set_initial_data(data)
|
||||
// open the new ui window
|
||||
ui.open()
|
||||
// auto update every five Master Controller tick
|
||||
ui.set_auto_update(5)
|
||||
|
||||
// Proc: Topic()
|
||||
// Parameters: 2 (standard Topic arguments)
|
||||
// Description: Responds to NanoUI button presses.
|
||||
/obj/item/device/communicator/Topic(href, href_list)
|
||||
if(..())
|
||||
return 1
|
||||
if(href_list["rename"])
|
||||
var/new_name = sanitizeSafe(input(usr,"Please enter your name.","Communicator",usr.name) )
|
||||
if(new_name)
|
||||
owner = new_name
|
||||
name = "[owner]'s [initial(name)]"
|
||||
if(camera)
|
||||
camera.name = name
|
||||
camera.c_tag = name
|
||||
|
||||
if(href_list["toggle_visibility"])
|
||||
switch(network_visibility)
|
||||
if(1) //Visible, becoming invisbile
|
||||
network_visibility = 0
|
||||
if(camera)
|
||||
camera.remove_network(NETWORK_COMMUNICATORS)
|
||||
if(0) //Invisible, becoming visible
|
||||
network_visibility = 1
|
||||
if(camera)
|
||||
camera.add_network(NETWORK_COMMUNICATORS)
|
||||
|
||||
if(href_list["toggle_ringer"])
|
||||
ringer = !ringer
|
||||
|
||||
if(href_list["add_hex"])
|
||||
var/hex = href_list["add_hex"]
|
||||
add_to_EPv2(hex)
|
||||
|
||||
if(href_list["write_target_address"])
|
||||
var/new_address = sanitizeSafe(input(usr,"Please enter the desired target EPv2 address. Note that you must write the colons \
|
||||
yourself.","Communicator",src.target_address) )
|
||||
if(new_address)
|
||||
target_address = new_address
|
||||
|
||||
if(href_list["clear_target_address"])
|
||||
target_address = ""
|
||||
|
||||
if(href_list["dial"])
|
||||
if(!get_connection_to_tcomms())
|
||||
usr << "<span class='danger'>Error: Cannot connect to Exonet node.</span>"
|
||||
return
|
||||
var/their_address = href_list["dial"]
|
||||
exonet.send_message(their_address, "voice")
|
||||
|
||||
if(href_list["decline"])
|
||||
var/ref_to_remove = href_list["decline"]
|
||||
var/atom/decline = locate(ref_to_remove)
|
||||
if(decline)
|
||||
del_request(decline)
|
||||
|
||||
if(href_list["message"])
|
||||
if(!get_connection_to_tcomms())
|
||||
usr << "<span class='danger'>Error: Cannot connect to Exonet node.</span>"
|
||||
return
|
||||
var/their_address = href_list["message"]
|
||||
var/text = sanitizeSafe(input(usr,"Enter your message.","Text Message"))
|
||||
if(text)
|
||||
exonet.send_message(their_address, "text", text)
|
||||
im_list += list(list("address" = exonet.address, "to_address" = their_address, "im" = text))
|
||||
log_pda("[usr] (COMM: [src]) sent \"[text]\" to [exonet.get_atom_from_address(their_address)]")
|
||||
for(var/mob/M in player_list)
|
||||
if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
|
||||
if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
|
||||
continue
|
||||
if(exonet.get_atom_from_address(their_address) == M)
|
||||
continue
|
||||
M.show_message("Comm IM - [src] -> [exonet.get_atom_from_address(their_address)]: [text]")
|
||||
|
||||
if(href_list["disconnect"])
|
||||
var/name_to_disconnect = href_list["disconnect"]
|
||||
for(var/mob/living/voice/V in contents)
|
||||
if(name_to_disconnect == V.name)
|
||||
close_connection(usr, V, "[usr] hung up")
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
if(name_to_disconnect == comm.name)
|
||||
close_connection(usr, comm, "[usr] hung up")
|
||||
|
||||
if(href_list["startvideo"])
|
||||
var/ref_to_video = href_list["startvideo"]
|
||||
var/obj/item/device/communicator/comm = locate(ref_to_video)
|
||||
if(comm)
|
||||
connect_video(usr, comm)
|
||||
|
||||
if(href_list["endvideo"])
|
||||
if(video_source)
|
||||
end_video()
|
||||
|
||||
if(href_list["watchvideo"])
|
||||
if(video_source)
|
||||
watch_video(usr,video_source.loc)
|
||||
|
||||
if(href_list["copy"])
|
||||
target_address = href_list["copy"]
|
||||
|
||||
if(href_list["copy_name"])
|
||||
target_address_name = href_list["copy_name"]
|
||||
|
||||
if(href_list["hang_up"])
|
||||
for(var/mob/living/voice/V in contents)
|
||||
close_connection(usr, V, "[usr] hung up")
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
close_connection(usr, comm, "[usr] hung up")
|
||||
|
||||
if(href_list["switch_tab"])
|
||||
selected_tab = href_list["switch_tab"]
|
||||
|
||||
if(href_list["edit"])
|
||||
var/n = input(usr, "Please enter message", name, notehtml)
|
||||
n = sanitizeSafe(n, extra = 0)
|
||||
if(n)
|
||||
note = html_decode(n)
|
||||
notehtml = note
|
||||
note = replacetext(note, "\n", "<br>")
|
||||
else
|
||||
note = ""
|
||||
notehtml = note
|
||||
|
||||
nanomanager.update_uis(src)
|
||||
add_fingerprint(usr)
|
||||
@@ -37,7 +37,8 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
|
||||
list("module" = "Contacts", "icon" = "person64", "number" = 3),
|
||||
list("module" = "Messaging", "icon" = "comment64", "number" = 4),
|
||||
list("module" = "Note", "icon" = "note64", "number" = 5),
|
||||
list("module" = "Settings", "icon" = "gear64", "number" = 6)
|
||||
list("module" = "Weather", "icon" = "sun64", "number" = 6),
|
||||
list("module" = "Settings", "icon" = "gear64", "number" = 7)
|
||||
) //list("module" = "Name of Module", "icon" = "icon name64", "number" = "what tab is the module")
|
||||
|
||||
var/selected_tab = 1
|
||||
@@ -191,16 +192,19 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
|
||||
if(istype(C, /obj/item/weapon/card/id))
|
||||
var/obj/item/weapon/card/id/idcard = C
|
||||
if(!idcard.registered_name || !idcard.assignment)
|
||||
user << "<span class='notice'>\The [src] rejects the ID.</span>"
|
||||
return
|
||||
if(!owner)
|
||||
user << "<span class='notice'>\The [src] rejects the ID.</span>"
|
||||
return
|
||||
if(owner == idcard.registered_name)
|
||||
to_chat(user, "<span class='notice'>\The [src] rejects the ID.</span>")
|
||||
else if(!owner)
|
||||
to_chat(user, "<span class='notice'>\The [src] rejects the ID.</span>")
|
||||
else if(owner == idcard.registered_name)
|
||||
occupation = idcard.assignment
|
||||
user << "<span class='notice'>Occupation updated.</span>"
|
||||
return
|
||||
else return
|
||||
to_chat(user, "<span class='notice'>Occupation updated.</span>")
|
||||
// else if(istype(C, /obj/item/weapon/cartridge))
|
||||
// if(cartridge)
|
||||
// to_chat(user, "<span class='notice'>\The [src] already has an external device attached!</span>")
|
||||
// else
|
||||
// modules.Add(list("module" = "External Device", "icon = external64", "number" = 8))
|
||||
// cartridge = C
|
||||
return
|
||||
|
||||
// Proc: attack_self()
|
||||
// Parameters: 1 (user - the mob that clicked the device in their hand)
|
||||
@@ -258,288 +262,6 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
|
||||
exonet = null
|
||||
return ..()
|
||||
|
||||
// Proc: ui_interact()
|
||||
// Parameters: 4 (standard NanoUI arguments)
|
||||
// Description: Uses a bunch of for loops to turn lists into lists of lists, so they can be displayed in nanoUI, then displays various buttons to the user.
|
||||
/obj/item/device/communicator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/key_state = null)
|
||||
// this is the data which will be sent to the ui
|
||||
var/data[0] //General nanoUI information
|
||||
var/communicators[0] //List of communicators
|
||||
var/invites[0] //Communicators and ghosts we've invited to our communicator.
|
||||
var/requests[0] //Communicators and ghosts wanting to go in our communicator.
|
||||
var/voices[0] //Current /mob/living/voice s inside the device.
|
||||
var/connected_communicators[0] //Current communicators connected to the device.
|
||||
|
||||
var/im_contacts_ui[0] //List of communicators that have been messaged.
|
||||
var/im_list_ui[0] //List of messages.
|
||||
|
||||
var/modules_ui[0] //Home screen info.
|
||||
|
||||
//First we add other 'local' communicators.
|
||||
for(var/obj/item/device/communicator/comm in known_devices)
|
||||
if(comm.network_visibility && comm.exonet)
|
||||
communicators[++communicators.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address)
|
||||
|
||||
//Now for ghosts who we pretend have communicators.
|
||||
for(var/mob/observer/dead/O in known_devices)
|
||||
if(O.client && O.client.prefs.communicator_visibility == 1 && O.exonet)
|
||||
communicators[++communicators.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
|
||||
|
||||
//Lists all the other communicators that we invited.
|
||||
for(var/obj/item/device/communicator/comm in voice_invites)
|
||||
if(comm.exonet)
|
||||
invites[++invites.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
|
||||
|
||||
//Ghosts we invited.
|
||||
for(var/mob/observer/dead/O in voice_invites)
|
||||
if(O.exonet && O.client)
|
||||
invites[++invites.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
|
||||
|
||||
//Communicators that want to talk to us.
|
||||
for(var/obj/item/device/communicator/comm in voice_requests)
|
||||
if(comm.exonet)
|
||||
requests[++requests.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
|
||||
|
||||
//Ghosts that want to talk to us.
|
||||
for(var/mob/observer/dead/O in voice_requests)
|
||||
if(O.exonet && O.client)
|
||||
requests[++requests.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
|
||||
|
||||
//Now for all the voice mobs inside the communicator.
|
||||
for(var/mob/living/voice/voice in contents)
|
||||
voices[++voices.len] = list("name" = sanitize("[voice.name]'s communicator"), "true_name" = sanitize(voice.name))
|
||||
|
||||
//Finally, all the communicators linked to this one.
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
connected_communicators[++connected_communicators.len] = list("name" = sanitize(comm.name), "true_name" = sanitize(comm.name), "ref" = "\ref[comm]")
|
||||
|
||||
//Devices that have been messaged or recieved messages from.
|
||||
for(var/obj/item/device/communicator/comm in im_contacts)
|
||||
if(comm.exonet)
|
||||
im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
|
||||
|
||||
for(var/mob/observer/dead/ghost in im_contacts)
|
||||
if(ghost.exonet)
|
||||
im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(ghost.name), "address" = ghost.exonet.address, "ref" = "\ref[ghost]")
|
||||
|
||||
//Actual messages.
|
||||
for(var/I in im_list)
|
||||
im_list_ui[++im_list_ui.len] = list("address" = I["address"], "to_address" = I["to_address"], "im" = I["im"])
|
||||
|
||||
//Modules for homescreen.
|
||||
for(var/list/R in modules)
|
||||
modules_ui[++modules_ui.len] = R
|
||||
|
||||
data["owner"] = owner ? owner : "Unset"
|
||||
data["occupation"] = occupation ? occupation : "Swipe ID to set."
|
||||
data["connectionStatus"] = get_connection_to_tcomms()
|
||||
data["visible"] = network_visibility
|
||||
data["address"] = exonet.address ? exonet.address : "Unallocated"
|
||||
data["targetAddress"] = target_address
|
||||
data["targetAddressName"] = target_address_name
|
||||
data["currentTab"] = selected_tab
|
||||
data["knownDevices"] = communicators
|
||||
data["invitesSent"] = invites
|
||||
data["requestsReceived"] = requests
|
||||
data["voice_mobs"] = voices
|
||||
data["communicating"] = connected_communicators
|
||||
data["video_comm"] = video_source ? "\ref[video_source.loc]" : null
|
||||
data["imContacts"] = im_contacts_ui
|
||||
data["imList"] = im_list_ui
|
||||
data["time"] = stationtime2text()
|
||||
data["ring"] = ringer
|
||||
data["homeScreen"] = modules_ui
|
||||
data["note"] = note // current notes
|
||||
|
||||
// update the ui if it exists, returns null if no ui is passed/found
|
||||
ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
|
||||
if(!ui)
|
||||
// the ui does not exist, so we'll create a new() one
|
||||
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
|
||||
ui = new(user, src, ui_key, "communicator.tmpl", "Communicator", 475, 700, state = key_state)
|
||||
// when the ui is first opened this is the data it will use
|
||||
ui.set_initial_data(data)
|
||||
// open the new ui window
|
||||
ui.open()
|
||||
// auto update every five Master Controller tick
|
||||
ui.set_auto_update(5)
|
||||
|
||||
// Proc: Topic()
|
||||
// Parameters: 2 (standard Topic arguments)
|
||||
// Description: Responds to NanoUI button presses.
|
||||
/obj/item/device/communicator/Topic(href, href_list)
|
||||
if(..())
|
||||
return 1
|
||||
if(href_list["rename"])
|
||||
var/new_name = sanitizeSafe(input(usr,"Please enter your name.","Communicator",usr.name) )
|
||||
if(new_name)
|
||||
owner = new_name
|
||||
name = "[owner]'s [initial(name)]"
|
||||
if(camera)
|
||||
camera.name = name
|
||||
camera.c_tag = name
|
||||
|
||||
if(href_list["toggle_visibility"])
|
||||
switch(network_visibility)
|
||||
if(1) //Visible, becoming invisbile
|
||||
network_visibility = 0
|
||||
if(camera)
|
||||
camera.remove_network(NETWORK_COMMUNICATORS)
|
||||
if(0) //Invisible, becoming visible
|
||||
network_visibility = 1
|
||||
if(camera)
|
||||
camera.add_network(NETWORK_COMMUNICATORS)
|
||||
|
||||
if(href_list["toggle_ringer"])
|
||||
ringer = !ringer
|
||||
|
||||
if(href_list["add_hex"])
|
||||
var/hex = href_list["add_hex"]
|
||||
add_to_EPv2(hex)
|
||||
|
||||
if(href_list["write_target_address"])
|
||||
var/new_address = sanitizeSafe(input(usr,"Please enter the desired target EPv2 address. Note that you must write the colons \
|
||||
yourself.","Communicator",src.target_address) )
|
||||
if(new_address)
|
||||
target_address = new_address
|
||||
|
||||
if(href_list["clear_target_address"])
|
||||
target_address = ""
|
||||
|
||||
if(href_list["dial"])
|
||||
if(!get_connection_to_tcomms())
|
||||
usr << "<span class='danger'>Error: Cannot connect to Exonet node.</span>"
|
||||
return
|
||||
var/their_address = href_list["dial"]
|
||||
exonet.send_message(their_address, "voice")
|
||||
|
||||
if(href_list["decline"])
|
||||
var/ref_to_remove = href_list["decline"]
|
||||
var/atom/decline = locate(ref_to_remove)
|
||||
if(decline)
|
||||
del_request(decline)
|
||||
|
||||
if(href_list["message"])
|
||||
if(!get_connection_to_tcomms())
|
||||
usr << "<span class='danger'>Error: Cannot connect to Exonet node.</span>"
|
||||
return
|
||||
var/their_address = href_list["message"]
|
||||
var/text = sanitizeSafe(input(usr,"Enter your message.","Text Message"))
|
||||
if(text)
|
||||
exonet.send_message(their_address, "text", text)
|
||||
im_list += list(list("address" = exonet.address, "to_address" = their_address, "im" = text))
|
||||
log_pda("[usr] (COMM: [src]) sent \"[text]\" to [exonet.get_atom_from_address(their_address)]")
|
||||
for(var/mob/M in player_list)
|
||||
if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
|
||||
if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
|
||||
continue
|
||||
if(exonet.get_atom_from_address(their_address) == M)
|
||||
continue
|
||||
M.show_message("Comm IM - [src] -> [exonet.get_atom_from_address(their_address)]: [text]")
|
||||
|
||||
if(href_list["disconnect"])
|
||||
var/name_to_disconnect = href_list["disconnect"]
|
||||
for(var/mob/living/voice/V in contents)
|
||||
if(name_to_disconnect == V.name)
|
||||
close_connection(usr, V, "[usr] hung up")
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
if(name_to_disconnect == comm.name)
|
||||
close_connection(usr, comm, "[usr] hung up")
|
||||
|
||||
if(href_list["startvideo"])
|
||||
var/ref_to_video = href_list["startvideo"]
|
||||
var/obj/item/device/communicator/comm = locate(ref_to_video)
|
||||
if(comm)
|
||||
connect_video(usr, comm)
|
||||
|
||||
if(href_list["endvideo"])
|
||||
if(video_source)
|
||||
end_video()
|
||||
|
||||
if(href_list["watchvideo"])
|
||||
if(video_source)
|
||||
watch_video(usr,video_source.loc)
|
||||
|
||||
if(href_list["copy"])
|
||||
target_address = href_list["copy"]
|
||||
|
||||
if(href_list["copy_name"])
|
||||
target_address_name = href_list["copy_name"]
|
||||
|
||||
if(href_list["hang_up"])
|
||||
for(var/mob/living/voice/V in contents)
|
||||
close_connection(usr, V, "[usr] hung up")
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
close_connection(usr, comm, "[usr] hung up")
|
||||
|
||||
if(href_list["switch_tab"])
|
||||
selected_tab = href_list["switch_tab"]
|
||||
|
||||
if(href_list["edit"])
|
||||
var/n = input(usr, "Please enter message", name, notehtml)
|
||||
n = sanitizeSafe(n, extra = 0)
|
||||
if(n)
|
||||
note = html_decode(n)
|
||||
notehtml = note
|
||||
note = replacetext(note, "\n", "<br>")
|
||||
else
|
||||
note = ""
|
||||
notehtml = note
|
||||
|
||||
nanomanager.update_uis(src)
|
||||
add_fingerprint(usr)
|
||||
|
||||
// Proc: receive_exonet_message()
|
||||
// Parameters: 4 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received,
|
||||
// text - message text to send if message is of type "text")
|
||||
// Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response and IM function.
|
||||
/obj/item/device/communicator/receive_exonet_message(var/atom/origin_atom, origin_address, message, text)
|
||||
if(message == "voice")
|
||||
if(isobserver(origin_atom) || istype(origin_atom, /obj/item/device/communicator))
|
||||
if(origin_atom in voice_invites)
|
||||
var/user = null
|
||||
if(ismob(origin_atom.loc))
|
||||
user = origin_atom.loc
|
||||
open_connection(user, origin_atom)
|
||||
return
|
||||
else if(origin_atom in voice_requests)
|
||||
return //Spam prevention
|
||||
else
|
||||
request(origin_atom)
|
||||
if(message == "ping")
|
||||
if(network_visibility)
|
||||
var/random = rand(200,350)
|
||||
random = random / 10
|
||||
exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
|
||||
if(message == "text")
|
||||
request_im(origin_atom, origin_address, text)
|
||||
return
|
||||
|
||||
// Proc: receive_exonet_message()
|
||||
// Parameters: 3 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received)
|
||||
// Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response.
|
||||
/mob/observer/dead/receive_exonet_message(origin_atom, origin_address, message, text)
|
||||
if(message == "voice")
|
||||
if(istype(origin_atom, /obj/item/device/communicator))
|
||||
var/obj/item/device/communicator/comm = origin_atom
|
||||
if(src in comm.voice_invites)
|
||||
comm.open_connection(src)
|
||||
return
|
||||
src << "<span class='notice'>\icon[origin_atom] Receiving communicator request from [origin_atom]. To answer, use the <b>Call Communicator</b> \
|
||||
verb, and select that name to answer the call.</span>"
|
||||
src << 'sound/machines/defib_SafetyOn.ogg'
|
||||
comm.voice_invites |= src
|
||||
if(message == "ping")
|
||||
if(client && client.prefs.communicator_visibility)
|
||||
var/random = rand(450,700)
|
||||
random = random / 10
|
||||
exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
|
||||
if(message == "text")
|
||||
src << "<span class='notice'>\icon[origin_atom] Received text message from [origin_atom]: <b>\"[text]\"</b></span>"
|
||||
src << 'sound/machines/defib_safetyOff.ogg'
|
||||
exonet_messages.Add("<b>From [origin_atom]:</b><br>[text]")
|
||||
return
|
||||
|
||||
// Proc: register_device()
|
||||
// Parameters: 1 (user - the person to use their name for)
|
||||
// Description: Updates the owner's name and the device's name.
|
||||
@@ -553,252 +275,13 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
|
||||
camera.name = name
|
||||
camera.c_tag = name
|
||||
|
||||
// Proc: add_communicating()
|
||||
// Parameters: 1 (comm - the communicator to add to communicating)
|
||||
// Description: Used when this communicator gets a new communicator to relay say/me messages to
|
||||
/obj/item/device/communicator/proc/add_communicating(obj/item/device/communicator/comm)
|
||||
if(!comm || !istype(comm)) return
|
||||
|
||||
communicating |= comm
|
||||
listening_objects |= src
|
||||
update_icon()
|
||||
|
||||
// Proc: del_communicating()
|
||||
// Parameters: 1 (comm - the communicator to remove from communicating)
|
||||
// Description: Used when this communicator is being asked to stop relaying say/me messages to another
|
||||
/obj/item/device/communicator/proc/del_communicating(obj/item/device/communicator/comm)
|
||||
if(!comm || !istype(comm)) return
|
||||
|
||||
communicating.Remove(comm)
|
||||
update_icon()
|
||||
|
||||
// Proc: open_connection()
|
||||
// Parameters: 2 (user - the person who initiated the connecting being opened, candidate - the communicator or observer that will connect to the device)
|
||||
// Description: Typechecks the candidate, then calls the correct proc for further connecting.
|
||||
/obj/item/device/communicator/proc/open_connection(mob/user, var/atom/candidate)
|
||||
if(isobserver(candidate))
|
||||
voice_invites.Remove(candidate)
|
||||
open_connection_to_ghost(user, candidate)
|
||||
else
|
||||
if(istype(candidate, /obj/item/device/communicator))
|
||||
open_connection_to_communicator(user, candidate)
|
||||
|
||||
// Proc: open_connection_to_communicator()
|
||||
// Parameters: 2 (user - the person who initiated this and will be receiving feedback information, candidate - someone else's communicator)
|
||||
// Description: Adds the candidate and src to each other's communicating lists, allowing messages seen by the devices to be relayed.
|
||||
/obj/item/device/communicator/proc/open_connection_to_communicator(mob/user, var/atom/candidate)
|
||||
if(!istype(candidate, /obj/item/device/communicator))
|
||||
return
|
||||
var/obj/item/device/communicator/comm = candidate
|
||||
voice_invites.Remove(candidate)
|
||||
comm.voice_requests.Remove(src)
|
||||
|
||||
if(user)
|
||||
comm.visible_message("<span class='notice'>\icon[src] Connecting to [src].</span>")
|
||||
user << "<span class='notice'>\icon[src] Attempting to call [comm].</span>"
|
||||
sleep(10)
|
||||
user << "<span class='notice'>\icon[src] Dialing internally from [station_name()], [system_name()].</span>" // Vorestation edit
|
||||
sleep(20) //If they don't have an exonet something is very wrong and we want a runtime.
|
||||
user << "<span class='notice'>\icon[src] Connection re-routed to [comm] at [comm.exonet.address].</span>"
|
||||
sleep(40)
|
||||
user << "<span class='notice'>\icon[src] Connection to [comm] at [comm.exonet.address] established.</span>"
|
||||
comm.visible_message("<span class='notice'>\icon[src] Connection to [src] at [exonet.address] established.</span>")
|
||||
sleep(20)
|
||||
|
||||
src.add_communicating(comm)
|
||||
comm.add_communicating(src)
|
||||
|
||||
// Proc: open_connection_to_ghost()
|
||||
// Parameters: 2 (user - the person who initiated this, candidate - the ghost that will be turned into a voice mob)
|
||||
// Description: Pulls the candidate ghost from deadchat, makes a new voice mob, transfers their identity, then their client.
|
||||
/obj/item/device/communicator/proc/open_connection_to_ghost(mob/user, var/mob/candidate)
|
||||
if(!isobserver(candidate))
|
||||
return
|
||||
//Handle moving the ghost into the new shell.
|
||||
announce_ghost_joinleave(candidate, 0, "They are occupying a personal communications device now.")
|
||||
voice_requests.Remove(candidate)
|
||||
voice_invites.Remove(candidate)
|
||||
var/mob/living/voice/new_voice = new /mob/living/voice(src) //Make the voice mob the ghost is going to be.
|
||||
new_voice.transfer_identity(candidate) //Now make the voice mob load from the ghost's active character in preferences.
|
||||
//Do some simple logging since this is a tad risky as a concept.
|
||||
var/msg = "[candidate && candidate.client ? "[candidate.client.key]" : "*no key*"] ([candidate]) has entered [src], triggered by \
|
||||
[user && user.client ? "[user.client.key]" : "*no key*"] ([user ? "[user]" : "*null*"]) at [x],[y],[z]. They have joined as [new_voice.name]."
|
||||
message_admins(msg)
|
||||
log_game(msg)
|
||||
new_voice.mind = candidate.mind //Transfer the mind, if any.
|
||||
new_voice.ckey = candidate.ckey //Finally, bring the client over.
|
||||
voice_mobs.Add(new_voice)
|
||||
listening_objects |= src
|
||||
|
||||
var/obj/screen/blackness = new() //Makes a black screen, so the candidate can't see what's going on before actually 'connecting' to the communicator.
|
||||
blackness.screen_loc = ui_entire_screen
|
||||
blackness.icon = 'icons/effects/effects.dmi'
|
||||
blackness.icon_state = "1"
|
||||
blackness.mouse_opacity = 2 //Can't see anything!
|
||||
new_voice.client.screen.Add(blackness)
|
||||
|
||||
update_icon()
|
||||
|
||||
//Now for some connection fluff.
|
||||
if(user)
|
||||
user << "<span class='notice'>\icon[src] Connecting to [candidate].</span>"
|
||||
new_voice << "<span class='notice'>\icon[src] Attempting to call [src].</span>"
|
||||
sleep(10)
|
||||
new_voice << "<span class='notice'>\icon[src] Dialing to [station_name()], Kara Subsystem, [system_name()].</span>"
|
||||
sleep(20)
|
||||
new_voice << "<span class='notice'>\icon[src] Connecting to [station_name()] telecommunications array.</span>"
|
||||
sleep(40)
|
||||
new_voice << "<span class='notice'>\icon[src] Connection to [station_name()] telecommunications array established. Redirecting signal to [src].</span>"
|
||||
sleep(20)
|
||||
|
||||
//We're connected, no need to hide everything.
|
||||
new_voice.client.screen.Remove(blackness)
|
||||
qdel(blackness)
|
||||
|
||||
new_voice << "<span class='notice'>\icon[src] Connection to [src] established.</span>"
|
||||
new_voice << "<b>To talk to the person on the other end of the call, just talk normally.</b>"
|
||||
new_voice << "<b>If you want to end the call, use the 'Hang Up' verb. The other person can also hang up at any time.</b>"
|
||||
new_voice << "<b>Remember, your character does not know anything you've learned from observing!</b>"
|
||||
if(new_voice.mind)
|
||||
new_voice.mind.assigned_role = "Disembodied Voice"
|
||||
if(user)
|
||||
user << "<span class='notice'>\icon[src] Your communicator is now connected to [candidate]'s communicator.</span>"
|
||||
|
||||
// Proc: close_connection()
|
||||
// Parameters: 3 (user - the user who initiated the disconnect, target - the mob or device being disconnected, reason - string shown when disconnected)
|
||||
// Description: Deletes specific voice_mobs or disconnects communicators, and shows a message to everyone when doing so. If target is null, all communicators
|
||||
// and voice mobs are removed.
|
||||
/obj/item/device/communicator/proc/close_connection(mob/user, var/atom/target, var/reason)
|
||||
if(voice_mobs.len == 0 && communicating.len == 0)
|
||||
return
|
||||
|
||||
for(var/mob/living/voice/voice in voice_mobs) //Handle ghost-callers
|
||||
if(target && voice != target) //If no target is inputted, it deletes all of them.
|
||||
continue
|
||||
voice << "<span class='danger'>\icon[src] [reason].</span>"
|
||||
visible_message("<span class='danger'>\icon[src] [reason].</span>")
|
||||
voice_mobs.Remove(voice)
|
||||
qdel(voice)
|
||||
update_icon()
|
||||
|
||||
for(var/obj/item/device/communicator/comm in communicating) //Now we handle real communicators.
|
||||
if(target && comm != target)
|
||||
continue
|
||||
src.del_communicating(comm)
|
||||
comm.del_communicating(src)
|
||||
comm.visible_message("<span class='danger'>\icon[src] [reason].</span>")
|
||||
visible_message("<span class='danger'>\icon[src] [reason].</span>")
|
||||
if(comm.camera && video_source == comm.camera) //We hung up on the person on video
|
||||
end_video()
|
||||
if(camera && comm.video_source == camera) //We hung up on them while they were watching us
|
||||
comm.end_video()
|
||||
|
||||
if(voice_mobs.len == 0 && communicating.len == 0)
|
||||
listening_objects.Remove(src)
|
||||
|
||||
// Proc: request()
|
||||
// Parameters: 1 (candidate - the ghost or communicator wanting to call the device)
|
||||
// Description: Response to a communicator or observer trying to call the device. Adds them to the list of requesters
|
||||
/obj/item/device/communicator/proc/request(var/atom/candidate)
|
||||
if(candidate in voice_requests)
|
||||
return
|
||||
var/who = null
|
||||
if(isobserver(candidate))
|
||||
who = candidate.name
|
||||
else if(istype(candidate, /obj/item/device/communicator))
|
||||
var/obj/item/device/communicator/comm = candidate
|
||||
who = comm.owner
|
||||
comm.voice_invites |= src
|
||||
|
||||
if(!who)
|
||||
return
|
||||
|
||||
voice_requests |= candidate
|
||||
|
||||
if(ringer)
|
||||
playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
for (var/mob/O in hearers(2, loc))
|
||||
O.show_message(text("\icon[src] *beep*"))
|
||||
|
||||
alert_called = 1
|
||||
update_icon()
|
||||
|
||||
//Search for holder of the device.
|
||||
var/mob/living/L = null
|
||||
if(loc && isliving(loc))
|
||||
L = loc
|
||||
|
||||
if(L)
|
||||
L << "<span class='notice'>\icon[src] Communications request from [who].</span>"
|
||||
|
||||
// Proc: del_request()
|
||||
// Parameters: 1 (candidate - the ghost or communicator to be declined)
|
||||
// Description: Declines a request and cleans up both ends
|
||||
/obj/item/device/communicator/proc/del_request(var/atom/candidate)
|
||||
if(!(candidate in voice_requests))
|
||||
return
|
||||
|
||||
if(isobserver(candidate))
|
||||
candidate << "<span class='warning'>Your communicator call request was declined.</span>"
|
||||
else if(istype(candidate, /obj/item/device/communicator))
|
||||
var/obj/item/device/communicator/comm = candidate
|
||||
comm.voice_invites -= src
|
||||
|
||||
voice_requests -= candidate
|
||||
|
||||
//Search for holder of our device.
|
||||
var/mob/living/us = null
|
||||
if(loc && isliving(loc))
|
||||
us = loc
|
||||
|
||||
if(us)
|
||||
us << "<span class='notice'>\icon[src] Declined request.</span>"
|
||||
|
||||
// Proc: request_im()
|
||||
// Parameters: 3 (candidate - the communicator wanting to message the device, origin_address - the address of the sender, text - the message)
|
||||
// Description: Response to a communicator trying to message the device.
|
||||
// Adds them to the list of people that have messaged this device and adds the message to the message list.
|
||||
/obj/item/device/communicator/proc/request_im(var/atom/candidate, var/origin_address, var/text)
|
||||
var/who = null
|
||||
if(isobserver(candidate))
|
||||
var/mob/observer/dead/ghost = candidate
|
||||
who = ghost
|
||||
im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
|
||||
else if(istype(candidate, /obj/item/device/communicator))
|
||||
var/obj/item/device/communicator/comm = candidate
|
||||
who = comm.owner
|
||||
comm.im_contacts |= src
|
||||
im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
|
||||
else return
|
||||
|
||||
im_contacts |= candidate
|
||||
|
||||
if(!who)
|
||||
return
|
||||
|
||||
if(ringer)
|
||||
playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
for (var/mob/O in hearers(2, loc))
|
||||
O.show_message(text("\icon[src] *beep*"))
|
||||
|
||||
alert_called = 1
|
||||
update_icon()
|
||||
|
||||
//Search for holder of the device.
|
||||
var/mob/living/L = null
|
||||
if(loc && isliving(loc))
|
||||
L = loc
|
||||
|
||||
if(L)
|
||||
L << "<span class='notice'>\icon[src] Message from [who].</span>"
|
||||
|
||||
// Proc: Destroy()
|
||||
// Parameters: None
|
||||
// Description: Deletes all the voice mobs, disconnects all linked communicators, and cuts lists to allow successful qdel()
|
||||
/obj/item/device/communicator/Destroy()
|
||||
for(var/mob/living/voice/voice in contents)
|
||||
voice_mobs.Remove(voice)
|
||||
voice << "<span class='danger'>\icon[src] Connection timed out with remote host.</span>"
|
||||
to_chat(voice, "<span class='danger'>\icon[src] Connection timed out with remote host.</span>")
|
||||
qdel(voice)
|
||||
close_connection(reason = "Connection timed out")
|
||||
communicating.Cut()
|
||||
@@ -832,314 +315,6 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
|
||||
|
||||
icon_state = initial(icon_state)
|
||||
|
||||
// Proc: see_emote()
|
||||
// Parameters: 2 (M - the mob the emote originated from, text - the emote's contents)
|
||||
// Description: Relays the emote to all linked communicators.
|
||||
/obj/item/device/communicator/see_emote(mob/living/M, text)
|
||||
var/rendered = "\icon[src] <span class='message'>[text]</span>"
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
var/turf/T = get_turf(comm)
|
||||
if(!T) return
|
||||
var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0) //Range of 3 since it's a tiny video display
|
||||
var/list/mobs_to_relay = in_range["mobs"]
|
||||
|
||||
for(var/mob/mob in mobs_to_relay) //We can't use visible_message(), or else we will get an infinite loop if two communicators hear each other.
|
||||
var/dst = get_dist(get_turf(mob),get_turf(comm))
|
||||
if(dst <= video_range)
|
||||
mob.show_message(rendered)
|
||||
else
|
||||
mob << "You can barely see some movement on \the [src]'s display."
|
||||
|
||||
..()
|
||||
|
||||
// Proc: hear_talk()
|
||||
// Parameters: 4 (M - the mob the speech originated from, text - what is being said, verb - the word used to describe how text is being said, speaking - language
|
||||
// being used)
|
||||
// Description: Relays the speech to all linked communicators.
|
||||
/obj/item/device/communicator/hear_talk(mob/living/M, text, verb, datum/language/speaking)
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
|
||||
var/turf/T = get_turf(comm)
|
||||
if(!T) return
|
||||
var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
|
||||
var/list/mobs_to_relay = in_range["mobs"]
|
||||
|
||||
for(var/mob/mob in mobs_to_relay)
|
||||
//Can whoever is hearing us understand?
|
||||
if(!mob.say_understands(M, speaking))
|
||||
if(speaking)
|
||||
text = speaking.scramble(text)
|
||||
else
|
||||
text = stars(text)
|
||||
var/name_used = M.GetVoice()
|
||||
var/rendered = null
|
||||
if(speaking) //Language being used
|
||||
rendered = "<span class='game say'>\icon[src] <span class='name'>[name_used]</span> [speaking.format_message(text, verb)]</span>"
|
||||
else
|
||||
rendered = "<span class='game say'>\icon[src] <span class='name'>[name_used]</span> [verb], <span class='message'>\"[text]\"</span></span>"
|
||||
mob.show_message(rendered, 2)
|
||||
|
||||
// Proc: show_message()
|
||||
// Parameters: 4 (msg - the message, type - number to determine if message is visible or audible, alt - unknown, alt_type - unknown)
|
||||
// Description: Relays the message to all linked communicators.
|
||||
/obj/item/device/communicator/show_message(msg, type, alt, alt_type)
|
||||
var/rendered = "\icon[src] <span class='message'>[msg]</span>"
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
var/turf/T = get_turf(comm)
|
||||
if(!T) return
|
||||
var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
|
||||
var/list/mobs_to_relay = in_range["mobs"]
|
||||
|
||||
for(var/mob/mob in mobs_to_relay)
|
||||
mob.show_message(rendered)
|
||||
..()
|
||||
|
||||
// Verb: join_as_voice()
|
||||
// Parameters: None
|
||||
// Description: Allows ghosts to call communicators, if they meet all the requirements.
|
||||
/mob/observer/dead/verb/join_as_voice()
|
||||
set category = "Ghost"
|
||||
set name = "Call Communicator"
|
||||
set desc = "If there is a communicator available, send a request to speak through it. This will reset your respawn timer, if someone picks up."
|
||||
|
||||
if(ticker.current_state < GAME_STATE_PLAYING)
|
||||
src << "<span class='danger'>The game hasn't started yet!</span>"
|
||||
return
|
||||
|
||||
if (!src.stat)
|
||||
return
|
||||
|
||||
if (usr != src)
|
||||
return //something is terribly wrong
|
||||
|
||||
var/confirm = alert(src, "Would you like to talk as [src.client.prefs.real_name], over a communicator? \
|
||||
This will reset your respawn timer, if someone answers.", "Join as Voice?", "Yes","No")
|
||||
if(confirm == "No")
|
||||
return
|
||||
|
||||
for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling.
|
||||
if(src.client.prefs.real_name == L.real_name)
|
||||
src << "<span class='danger'>Your identity is already present in the game world. Please load in a different character first.</span>"
|
||||
return
|
||||
|
||||
var/obj/machinery/exonet_node/E = get_exonet_node()
|
||||
if(!E || !E.on || !E.allow_external_communicators)
|
||||
src << "<span class='danger'>The Exonet node at telecommunications is down at the moment, or is actively blocking you, so your call can't go through.</span>"
|
||||
return
|
||||
|
||||
var/list/choices = list()
|
||||
for(var/obj/item/device/communicator/comm in all_communicators)
|
||||
if(!comm.network_visibility || !comm.exonet || !comm.exonet.address)
|
||||
continue
|
||||
choices.Add(comm)
|
||||
|
||||
if(!choices.len)
|
||||
src << "<span class='danger'>There are no available communicators, sorry.</span>"
|
||||
return
|
||||
|
||||
var/choice = input(src,"Send a voice request to whom?") as null|anything in choices
|
||||
if(choice)
|
||||
var/obj/item/device/communicator/chosen_communicator = choice
|
||||
var/mob/observer/dead/O = src
|
||||
if(O.exonet)
|
||||
O.exonet.send_message(chosen_communicator.exonet.address, "voice")
|
||||
|
||||
src << "A communications request has been sent to [chosen_communicator]. Now you need to wait until someone answers."
|
||||
|
||||
// Verb: text_communicator()
|
||||
// Parameters: None
|
||||
// Description: Allows a ghost to send a text message to a communicator.
|
||||
/mob/observer/dead/verb/text_communicator()
|
||||
set category = "Ghost"
|
||||
set name = "Text Communicator"
|
||||
set desc = "If there is a communicator available, send a text message to it."
|
||||
|
||||
if(ticker.current_state < GAME_STATE_PLAYING)
|
||||
src << "<span class='danger'>The game hasn't started yet!</span>"
|
||||
return
|
||||
|
||||
if (!src.stat)
|
||||
return
|
||||
|
||||
if (usr != src)
|
||||
return //something is terribly wrong
|
||||
|
||||
for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling.
|
||||
if(src.client.prefs.real_name == L.real_name)
|
||||
src << "<span class='danger'>Your identity is already present in the game world. Please load in a different character first.</span>"
|
||||
return
|
||||
|
||||
var/obj/machinery/exonet_node/E = get_exonet_node()
|
||||
if(!E || !E.on || !E.allow_external_communicators)
|
||||
src << "<span class='danger'>The Exonet node at telecommunications is down at the moment, or is actively blocking you, so your call can't go through.</span>"
|
||||
return
|
||||
|
||||
var/list/choices = list()
|
||||
for(var/obj/item/device/communicator/comm in all_communicators)
|
||||
if(!comm.network_visibility || !comm.exonet || !comm.exonet.address)
|
||||
continue
|
||||
choices.Add(comm)
|
||||
|
||||
if(!choices.len)
|
||||
src << "<span class='danger'>There are no available communicators, sorry.</span>"
|
||||
return
|
||||
|
||||
var/choice = input(src,"Send a text message to whom?") as null|anything in choices
|
||||
if(choice)
|
||||
var/obj/item/device/communicator/chosen_communicator = choice
|
||||
var/mob/observer/dead/O = src
|
||||
var/text_message = sanitize(input(src, "What do you want the message to say?")) as message
|
||||
if(text_message && O.exonet)
|
||||
O.exonet.send_message(chosen_communicator.exonet.address, "text", text_message)
|
||||
|
||||
src << "<span class='notice'>You have sent '[text_message]' to [chosen_communicator].</span>"
|
||||
exonet_messages.Add("<b>To [chosen_communicator]:</b><br>[text_message]")
|
||||
log_pda("[usr] (COMM: [src]) sent \"[text_message]\" to [chosen_communicator]")
|
||||
for(var/mob/M in player_list)
|
||||
if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
|
||||
if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
|
||||
continue
|
||||
if(M == src)
|
||||
continue
|
||||
M.show_message("Comm IM - [src] -> [chosen_communicator]: [text_message]")
|
||||
|
||||
|
||||
|
||||
// Verb: show_text_messages()
|
||||
// Parameters: None
|
||||
// Description: Lets ghosts review messages they've sent or received.
|
||||
/mob/observer/dead/verb/show_text_messages()
|
||||
set category = "Ghost"
|
||||
set name = "Show Text Messages"
|
||||
set desc = "Allows you to see exonet text messages you've sent and received."
|
||||
|
||||
var/HTML = "<html><head><title>Exonet Message Log</title></head><body>"
|
||||
for(var/line in exonet_messages)
|
||||
HTML += line + "<br>"
|
||||
HTML +="</body></html>"
|
||||
usr << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0")
|
||||
|
||||
// Proc: connect_video()
|
||||
// Parameters: user - the mob doing the viewing of video, comm - the communicator at the far end
|
||||
// Description: Sets up a videocall and puts the first view into it using watch_video, and updates the icon
|
||||
/obj/item/device/communicator/proc/connect_video(mob/user,obj/item/device/communicator/comm)
|
||||
if((!user) || (!comm) || user.stat) return //KO or dead, or already in a video
|
||||
|
||||
if(video_source) //Already in a video
|
||||
user << "<span class='danger'>You are already connected to a video call!</span>"
|
||||
return
|
||||
|
||||
if(user.blinded) //User is blinded
|
||||
user << "<span class='danger'>You cannot see well enough to do that!</span>"
|
||||
return
|
||||
|
||||
if(!(src in comm.communicating) || !comm.camera) //You called someone with a broken communicator or one that's fake or yourself or something
|
||||
user << "<span class='danger'>\icon[src]ERROR: Video failed. Either bandwidth is too low, or the other communicator is malfunctioning.</span>"
|
||||
return
|
||||
|
||||
var/turf/t1 = get_turf(src)
|
||||
var/turf/t2 = get_turf(comm)
|
||||
if(!is_on_same_plane_or_station(t1.z, t2.z) || !video_source.can_use())
|
||||
user << "<span class='danger'>Request to establish video timed out!</span>"
|
||||
return
|
||||
|
||||
user << "<span class='notice'>\icon[src] Attempting to start video over existing call.</span>"
|
||||
sleep(30)
|
||||
user << "<span class='notice'>\icon[src] Please wait...</span>"
|
||||
|
||||
video_source = comm.camera
|
||||
comm.visible_message("<span class='danger'>\icon[src] New video connection from [comm].</span>")
|
||||
watch_video(user)
|
||||
update_icon()
|
||||
|
||||
// Proc: watch_video()
|
||||
// Parameters: user - the mob doing the viewing of video
|
||||
// Description: Moves a mob's eye to the far end for the duration of viewing the far end
|
||||
/obj/item/device/communicator/proc/watch_video(mob/user)
|
||||
if(!Adjacent(user) || !video_source) return
|
||||
user.set_machine(video_source)
|
||||
user.reset_view(video_source)
|
||||
to_chat(user,"<span class='notice'>Now viewing video session. To leave camera view, close the communicator window OR: OOC -> Cancel Camera View</span>")
|
||||
to_chat(user,"<span class='notice'>To return to an active video session, use the communicator in your hand.</span>")
|
||||
spawn(0)
|
||||
while(user.machine == video_source && (Adjacent(user) || loc == user))
|
||||
var/turf/T = get_turf(video_source)
|
||||
if(!T || !is_on_same_plane_or_station(T.z, user.z) || !video_source.can_use())
|
||||
user << "<span class='warning'>The screen bursts into static, then goes black.</span>"
|
||||
video_cleanup(user)
|
||||
return
|
||||
sleep(10)
|
||||
|
||||
video_cleanup(user)
|
||||
|
||||
// Proc: video_cleanup()
|
||||
// Parameters: user - the mob who doesn't want to see video anymore
|
||||
// Description: Cleans up mob's client when they stop watching a video
|
||||
/obj/item/device/communicator/proc/video_cleanup(mob/user)
|
||||
if(!user) return
|
||||
|
||||
user.reset_view(null)
|
||||
user.unset_machine()
|
||||
|
||||
// Proc: end_video()
|
||||
// Parameters: reason - the text reason to print for why it ended
|
||||
// Description: Ends the video call by clearing video_source
|
||||
/obj/item/device/communicator/proc/end_video(var/reason)
|
||||
video_source = null
|
||||
|
||||
. = "<span class='danger'>\icon[src] [reason ? reason : "Video session ended"].</span>"
|
||||
|
||||
visible_message(.)
|
||||
update_icon()
|
||||
|
||||
//For synths who have no hands.
|
||||
/obj/item/device/communicator/integrated
|
||||
name = "integrated communicator"
|
||||
desc = "A circuit used for long-range communications, able to be integrated into a system."
|
||||
|
||||
//A stupid hack because synths don't use languages properly or something.
|
||||
//I don't want to go digging in saycode for a week, so BS it as translation software or something.
|
||||
|
||||
// Proc: open_connection_to_ghost()
|
||||
// Parameters: 2 (refer to base definition for arguments)
|
||||
// Description: Synths don't use languages properly, so this is a bandaid fix until that can be resolved..
|
||||
/obj/item/device/communicator/integrated/open_connection_to_ghost(user, candidate)
|
||||
..(user, candidate)
|
||||
spawn(1)
|
||||
for(var/mob/living/voice/V in contents)
|
||||
V.universal_speak = 1
|
||||
V.universal_understand = 1
|
||||
|
||||
// Verb: activate()
|
||||
// Parameters: None
|
||||
// Description: Lets synths use their communicators without hands.
|
||||
/obj/item/device/communicator/integrated/verb/activate()
|
||||
set category = "AI IM"
|
||||
set name = "Use Communicator"
|
||||
set desc = "Utilizes your built-in communicator."
|
||||
set src in usr
|
||||
|
||||
if(usr.stat == 2)
|
||||
usr << "You can't do that because you are dead!"
|
||||
return
|
||||
|
||||
src.attack_self(usr)
|
||||
|
||||
// Verb: activate()
|
||||
// Parameters: None
|
||||
// Description: Lets synths use their communicators without hands.
|
||||
/obj/item/device/communicator/integrated/verb/see_video()
|
||||
set category = "AI IM"
|
||||
set name = "View Comm. Video"
|
||||
set desc = "Utilizes your built-in communicator."
|
||||
set src in usr
|
||||
|
||||
if(usr.stat == 2)
|
||||
usr << "You can't do that because you are dead!"
|
||||
return
|
||||
|
||||
src.watch_video(usr)
|
||||
|
||||
// A camera preset for spawning in the communicator
|
||||
/obj/machinery/camera/communicator
|
||||
network = list(NETWORK_COMMUNICATORS)
|
||||
|
||||
32
code/game/objects/items/devices/communicator/integrated.dm
Normal file
@@ -0,0 +1,32 @@
|
||||
//For synths who have no hands.
|
||||
/obj/item/device/communicator/integrated
|
||||
name = "integrated communicator"
|
||||
desc = "A circuit used for long-range communications, able to be integrated into a system."
|
||||
|
||||
//A stupid hack because synths don't use languages properly or something.
|
||||
//I don't want to go digging in saycode for a week, so BS it as translation software or something.
|
||||
|
||||
// Proc: open_connection_to_ghost()
|
||||
// Parameters: 2 (refer to base definition for arguments)
|
||||
// Description: Synths don't use languages properly, so this is a bandaid fix until that can be resolved..
|
||||
/obj/item/device/communicator/integrated/open_connection_to_ghost(user, candidate)
|
||||
..(user, candidate)
|
||||
spawn(1)
|
||||
for(var/mob/living/voice/V in contents)
|
||||
V.universal_speak = 1
|
||||
V.universal_understand = 1
|
||||
|
||||
// Verb: activate()
|
||||
// Parameters: None
|
||||
// Description: Lets synths use their communicators without hands.
|
||||
/obj/item/device/communicator/integrated/verb/activate()
|
||||
set category = "AI IM"
|
||||
set name = "Use Communicator"
|
||||
set desc = "Utilizes your built-in communicator."
|
||||
set src in usr
|
||||
|
||||
if(usr.stat == 2)
|
||||
to_chat(usr, "You can't do that because you are dead!")
|
||||
return
|
||||
|
||||
src.attack_self(usr)
|
||||
162
code/game/objects/items/devices/communicator/messaging.dm
Normal file
@@ -0,0 +1,162 @@
|
||||
// Proc: receive_exonet_message()
|
||||
// Parameters: 4 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received,
|
||||
// text - message text to send if message is of type "text")
|
||||
// Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response and IM function.
|
||||
/obj/item/device/communicator/receive_exonet_message(var/atom/origin_atom, origin_address, message, text)
|
||||
if(message == "voice")
|
||||
if(isobserver(origin_atom) || istype(origin_atom, /obj/item/device/communicator))
|
||||
if(origin_atom in voice_invites)
|
||||
var/user = null
|
||||
if(ismob(origin_atom.loc))
|
||||
user = origin_atom.loc
|
||||
open_connection(user, origin_atom)
|
||||
return
|
||||
else if(origin_atom in voice_requests)
|
||||
return //Spam prevention
|
||||
else
|
||||
request(origin_atom)
|
||||
if(message == "ping")
|
||||
if(network_visibility)
|
||||
var/random = rand(200,350)
|
||||
random = random / 10
|
||||
exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
|
||||
if(message == "text")
|
||||
request_im(origin_atom, origin_address, text)
|
||||
return
|
||||
|
||||
// Proc: receive_exonet_message()
|
||||
// Parameters: 3 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received)
|
||||
// Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response.
|
||||
/mob/observer/dead/receive_exonet_message(origin_atom, origin_address, message, text)
|
||||
if(message == "voice")
|
||||
if(istype(origin_atom, /obj/item/device/communicator))
|
||||
var/obj/item/device/communicator/comm = origin_atom
|
||||
if(src in comm.voice_invites)
|
||||
comm.open_connection(src)
|
||||
return
|
||||
to_chat(src, "<span class='notice'>\icon[origin_atom] Receiving communicator request from [origin_atom]. To answer, use the <b>Call Communicator</b> \
|
||||
verb, and select that name to answer the call.</span>")
|
||||
src << 'sound/machines/defib_SafetyOn.ogg'
|
||||
comm.voice_invites |= src
|
||||
if(message == "ping")
|
||||
if(client && client.prefs.communicator_visibility)
|
||||
var/random = rand(450,700)
|
||||
random = random / 10
|
||||
exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
|
||||
if(message == "text")
|
||||
to_chat(src, "<span class='notice'>\icon[origin_atom] Received text message from [origin_atom]: <b>\"[text]\"</b></span>")
|
||||
src << 'sound/machines/defib_safetyOff.ogg'
|
||||
exonet_messages.Add("<b>From [origin_atom]:</b><br>[text]")
|
||||
return
|
||||
|
||||
// Proc: request_im()
|
||||
// Parameters: 3 (candidate - the communicator wanting to message the device, origin_address - the address of the sender, text - the message)
|
||||
// Description: Response to a communicator trying to message the device.
|
||||
// Adds them to the list of people that have messaged this device and adds the message to the message list.
|
||||
/obj/item/device/communicator/proc/request_im(var/atom/candidate, var/origin_address, var/text)
|
||||
var/who = null
|
||||
if(isobserver(candidate))
|
||||
var/mob/observer/dead/ghost = candidate
|
||||
who = ghost
|
||||
im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
|
||||
else if(istype(candidate, /obj/item/device/communicator))
|
||||
var/obj/item/device/communicator/comm = candidate
|
||||
who = comm.owner
|
||||
comm.im_contacts |= src
|
||||
im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
|
||||
else return
|
||||
|
||||
im_contacts |= candidate
|
||||
|
||||
if(!who)
|
||||
return
|
||||
|
||||
if(ringer)
|
||||
playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
for (var/mob/O in hearers(2, loc))
|
||||
O.show_message(text("\icon[src] *beep*"))
|
||||
|
||||
alert_called = 1
|
||||
update_icon()
|
||||
|
||||
//Search for holder of the device.
|
||||
var/mob/living/L = null
|
||||
if(loc && isliving(loc))
|
||||
L = loc
|
||||
|
||||
if(L)
|
||||
to_chat(L, "<span class='notice'>\icon[src] Message from [who].</span>")
|
||||
|
||||
// Verb: text_communicator()
|
||||
// Parameters: None
|
||||
// Description: Allows a ghost to send a text message to a communicator.
|
||||
/mob/observer/dead/verb/text_communicator()
|
||||
set category = "Ghost"
|
||||
set name = "Text Communicator"
|
||||
set desc = "If there is a communicator available, send a text message to it."
|
||||
|
||||
if(ticker.current_state < GAME_STATE_PLAYING)
|
||||
to_chat(src, "<span class='danger'>The game hasn't started yet!</span>")
|
||||
return
|
||||
|
||||
if (!src.stat)
|
||||
return
|
||||
|
||||
if (usr != src)
|
||||
return //something is terribly wrong
|
||||
|
||||
for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling.
|
||||
if(src.client.prefs.real_name == L.real_name)
|
||||
to_chat(src, "<span class='danger'>Your identity is already present in the game world. Please load in a different character first.</span>")
|
||||
return
|
||||
|
||||
var/obj/machinery/exonet_node/E = get_exonet_node()
|
||||
if(!E || !E.on || !E.allow_external_communicators)
|
||||
to_chat(src, "<span class='danger'>The Exonet node at telecommunications is down at the moment, or is actively blocking you, \
|
||||
so your call can't go through.</span>")
|
||||
return
|
||||
|
||||
var/list/choices = list()
|
||||
for(var/obj/item/device/communicator/comm in all_communicators)
|
||||
if(!comm.network_visibility || !comm.exonet || !comm.exonet.address)
|
||||
continue
|
||||
choices.Add(comm)
|
||||
|
||||
if(!choices.len)
|
||||
to_chat(src, "<span class='danger'>There are no available communicators, sorry.</span>")
|
||||
return
|
||||
|
||||
var/choice = input(src,"Send a text message to whom?") as null|anything in choices
|
||||
if(choice)
|
||||
var/obj/item/device/communicator/chosen_communicator = choice
|
||||
var/mob/observer/dead/O = src
|
||||
var/text_message = sanitize(input(src, "What do you want the message to say?")) as message
|
||||
if(text_message && O.exonet)
|
||||
O.exonet.send_message(chosen_communicator.exonet.address, "text", text_message)
|
||||
|
||||
to_chat(src, "<span class='notice'>You have sent '[text_message]' to [chosen_communicator].</span>")
|
||||
exonet_messages.Add("<b>To [chosen_communicator]:</b><br>[text_message]")
|
||||
log_pda("[usr] (COMM: [src]) sent \"[text_message]\" to [chosen_communicator]")
|
||||
for(var/mob/M in player_list)
|
||||
if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
|
||||
if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
|
||||
continue
|
||||
if(M == src)
|
||||
continue
|
||||
M.show_message("Comm IM - [src] -> [chosen_communicator]: [text_message]")
|
||||
|
||||
|
||||
|
||||
// Verb: show_text_messages()
|
||||
// Parameters: None
|
||||
// Description: Lets ghosts review messages they've sent or received.
|
||||
/mob/observer/dead/verb/show_text_messages()
|
||||
set category = "Ghost"
|
||||
set name = "Show Text Messages"
|
||||
set desc = "Allows you to see exonet text messages you've sent and received."
|
||||
|
||||
var/HTML = "<html><head><title>Exonet Message Log</title></head><body>"
|
||||
for(var/line in exonet_messages)
|
||||
HTML += line + "<br>"
|
||||
HTML +="</body></html>"
|
||||
usr << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0")
|
||||
380
code/game/objects/items/devices/communicator/phone.dm
Normal file
@@ -0,0 +1,380 @@
|
||||
// Proc: add_communicating()
|
||||
// Parameters: 1 (comm - the communicator to add to communicating)
|
||||
// Description: Used when this communicator gets a new communicator to relay say/me messages to
|
||||
/obj/item/device/communicator/proc/add_communicating(obj/item/device/communicator/comm)
|
||||
if(!comm || !istype(comm)) return
|
||||
|
||||
communicating |= comm
|
||||
listening_objects |= src
|
||||
update_icon()
|
||||
|
||||
// Proc: del_communicating()
|
||||
// Parameters: 1 (comm - the communicator to remove from communicating)
|
||||
// Description: Used when this communicator is being asked to stop relaying say/me messages to another
|
||||
/obj/item/device/communicator/proc/del_communicating(obj/item/device/communicator/comm)
|
||||
if(!comm || !istype(comm)) return
|
||||
|
||||
communicating.Remove(comm)
|
||||
update_icon()
|
||||
|
||||
// Proc: open_connection()
|
||||
// Parameters: 2 (user - the person who initiated the connecting being opened, candidate - the communicator or observer that will connect to the device)
|
||||
// Description: Typechecks the candidate, then calls the correct proc for further connecting.
|
||||
/obj/item/device/communicator/proc/open_connection(mob/user, var/atom/candidate)
|
||||
if(isobserver(candidate))
|
||||
voice_invites.Remove(candidate)
|
||||
open_connection_to_ghost(user, candidate)
|
||||
else
|
||||
if(istype(candidate, /obj/item/device/communicator))
|
||||
open_connection_to_communicator(user, candidate)
|
||||
|
||||
// Proc: open_connection_to_communicator()
|
||||
// Parameters: 2 (user - the person who initiated this and will be receiving feedback information, candidate - someone else's communicator)
|
||||
// Description: Adds the candidate and src to each other's communicating lists, allowing messages seen by the devices to be relayed.
|
||||
/obj/item/device/communicator/proc/open_connection_to_communicator(mob/user, var/atom/candidate)
|
||||
if(!istype(candidate, /obj/item/device/communicator))
|
||||
return
|
||||
var/obj/item/device/communicator/comm = candidate
|
||||
voice_invites.Remove(candidate)
|
||||
comm.voice_requests.Remove(src)
|
||||
|
||||
if(user)
|
||||
comm.visible_message("<span class='notice'>\icon[src] Connecting to [src].</span>")
|
||||
to_chat(user, "<span class='notice'>\icon[src] Attempting to call [comm].</span>")
|
||||
sleep(10)
|
||||
to_chat(user, "<span class='notice'>\icon[src] Dialing internally from [station_name()], [system_name()].</span>") // Vorestation edit
|
||||
sleep(20) //If they don't have an exonet something is very wrong and we want a runtime.
|
||||
to_chat(user, "<span class='notice'>\icon[src] Connection re-routed to [comm] at [comm.exonet.address].</span>")
|
||||
sleep(40)
|
||||
to_chat(user, "<span class='notice'>\icon[src] Connection to [comm] at [comm.exonet.address] established.</span>")
|
||||
comm.visible_message("<span class='notice'>\icon[src] Connection to [src] at [exonet.address] established.</span>")
|
||||
sleep(20)
|
||||
|
||||
src.add_communicating(comm)
|
||||
comm.add_communicating(src)
|
||||
|
||||
// Proc: open_connection_to_ghost()
|
||||
// Parameters: 2 (user - the person who initiated this, candidate - the ghost that will be turned into a voice mob)
|
||||
// Description: Pulls the candidate ghost from deadchat, makes a new voice mob, transfers their identity, then their client.
|
||||
/obj/item/device/communicator/proc/open_connection_to_ghost(mob/user, var/mob/candidate)
|
||||
if(!isobserver(candidate))
|
||||
return
|
||||
//Handle moving the ghost into the new shell.
|
||||
announce_ghost_joinleave(candidate, 0, "They are occupying a personal communications device now.")
|
||||
voice_requests.Remove(candidate)
|
||||
voice_invites.Remove(candidate)
|
||||
var/mob/living/voice/new_voice = new /mob/living/voice(src) //Make the voice mob the ghost is going to be.
|
||||
new_voice.transfer_identity(candidate) //Now make the voice mob load from the ghost's active character in preferences.
|
||||
//Do some simple logging since this is a tad risky as a concept.
|
||||
var/msg = "[candidate && candidate.client ? "[candidate.client.key]" : "*no key*"] ([candidate]) has entered [src], triggered by \
|
||||
[user && user.client ? "[user.client.key]" : "*no key*"] ([user ? "[user]" : "*null*"]) at [x],[y],[z]. They have joined as [new_voice.name]."
|
||||
message_admins(msg)
|
||||
log_game(msg)
|
||||
new_voice.mind = candidate.mind //Transfer the mind, if any.
|
||||
new_voice.ckey = candidate.ckey //Finally, bring the client over.
|
||||
voice_mobs.Add(new_voice)
|
||||
listening_objects |= src
|
||||
|
||||
var/obj/screen/blackness = new() //Makes a black screen, so the candidate can't see what's going on before actually 'connecting' to the communicator.
|
||||
blackness.screen_loc = ui_entire_screen
|
||||
blackness.icon = 'icons/effects/effects.dmi'
|
||||
blackness.icon_state = "1"
|
||||
blackness.mouse_opacity = 2 //Can't see anything!
|
||||
new_voice.client.screen.Add(blackness)
|
||||
|
||||
update_icon()
|
||||
|
||||
//Now for some connection fluff.
|
||||
if(user)
|
||||
to_chat(user, "<span class='notice'>\icon[src] Connecting to [candidate].</span>")
|
||||
to_chat(new_voice, "<span class='notice'>\icon[src] Attempting to call [src].</span>")
|
||||
sleep(10)
|
||||
to_chat(new_voice, "<span class='notice'>\icon[src] Dialing to [station_name()], Kara Subsystem, [system_name()].</span>")
|
||||
sleep(20)
|
||||
to_chat(new_voice, "<span class='notice'>\icon[src] Connecting to [station_name()] telecommunications array.</span>")
|
||||
sleep(40)
|
||||
to_chat(new_voice, "<span class='notice'>\icon[src] Connection to [station_name()] telecommunications array established. Redirecting signal to [src].</span>")
|
||||
sleep(20)
|
||||
|
||||
//We're connected, no need to hide everything.
|
||||
new_voice.client.screen.Remove(blackness)
|
||||
qdel(blackness)
|
||||
|
||||
to_chat(new_voice, "<span class='notice'>\icon[src] Connection to [src] established.</span>")
|
||||
to_chat(new_voice, "<b>To talk to the person on the other end of the call, just talk normally.</b>")
|
||||
to_chat(new_voice, "<b>If you want to end the call, use the 'Hang Up' verb. The other person can also hang up at any time.</b>")
|
||||
to_chat(new_voice, "<b>Remember, your character does not know anything you've learned from observing!</b>")
|
||||
if(new_voice.mind)
|
||||
new_voice.mind.assigned_role = "Disembodied Voice"
|
||||
if(user)
|
||||
to_chat(user, "<span class='notice'>\icon[src] Your communicator is now connected to [candidate]'s communicator.</span>")
|
||||
|
||||
// Proc: close_connection()
|
||||
// Parameters: 3 (user - the user who initiated the disconnect, target - the mob or device being disconnected, reason - string shown when disconnected)
|
||||
// Description: Deletes specific voice_mobs or disconnects communicators, and shows a message to everyone when doing so. If target is null, all communicators
|
||||
// and voice mobs are removed.
|
||||
/obj/item/device/communicator/proc/close_connection(mob/user, var/atom/target, var/reason)
|
||||
if(voice_mobs.len == 0 && communicating.len == 0)
|
||||
return
|
||||
|
||||
for(var/mob/living/voice/voice in voice_mobs) //Handle ghost-callers
|
||||
if(target && voice != target) //If no target is inputted, it deletes all of them.
|
||||
continue
|
||||
to_chat(voice, "<span class='danger'>\icon[src] [reason].</span>")
|
||||
visible_message("<span class='danger'>\icon[src] [reason].</span>")
|
||||
voice_mobs.Remove(voice)
|
||||
qdel(voice)
|
||||
update_icon()
|
||||
|
||||
for(var/obj/item/device/communicator/comm in communicating) //Now we handle real communicators.
|
||||
if(target && comm != target)
|
||||
continue
|
||||
src.del_communicating(comm)
|
||||
comm.del_communicating(src)
|
||||
comm.visible_message("<span class='danger'>\icon[src] [reason].</span>")
|
||||
visible_message("<span class='danger'>\icon[src] [reason].</span>")
|
||||
if(comm.camera && video_source == comm.camera) //We hung up on the person on video
|
||||
end_video()
|
||||
if(camera && comm.video_source == camera) //We hung up on them while they were watching us
|
||||
comm.end_video()
|
||||
|
||||
if(voice_mobs.len == 0 && communicating.len == 0)
|
||||
listening_objects.Remove(src)
|
||||
|
||||
// Proc: request()
|
||||
// Parameters: 1 (candidate - the ghost or communicator wanting to call the device)
|
||||
// Description: Response to a communicator or observer trying to call the device. Adds them to the list of requesters
|
||||
/obj/item/device/communicator/proc/request(var/atom/candidate)
|
||||
if(candidate in voice_requests)
|
||||
return
|
||||
var/who = null
|
||||
if(isobserver(candidate))
|
||||
who = candidate.name
|
||||
else if(istype(candidate, /obj/item/device/communicator))
|
||||
var/obj/item/device/communicator/comm = candidate
|
||||
who = comm.owner
|
||||
comm.voice_invites |= src
|
||||
|
||||
if(!who)
|
||||
return
|
||||
|
||||
voice_requests |= candidate
|
||||
|
||||
if(ringer)
|
||||
playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
for (var/mob/O in hearers(2, loc))
|
||||
O.show_message(text("\icon[src] *beep*"))
|
||||
|
||||
alert_called = 1
|
||||
update_icon()
|
||||
|
||||
//Search for holder of the device.
|
||||
var/mob/living/L = null
|
||||
if(loc && isliving(loc))
|
||||
L = loc
|
||||
|
||||
if(L)
|
||||
to_chat(L, "<span class='notice'>\icon[src] Communications request from [who].</span>")
|
||||
|
||||
// Proc: del_request()
|
||||
// Parameters: 1 (candidate - the ghost or communicator to be declined)
|
||||
// Description: Declines a request and cleans up both ends
|
||||
/obj/item/device/communicator/proc/del_request(var/atom/candidate)
|
||||
if(!(candidate in voice_requests))
|
||||
return
|
||||
|
||||
if(isobserver(candidate))
|
||||
to_chat(candidate, "<span class='warning'>Your communicator call request was declined.</span>")
|
||||
else if(istype(candidate, /obj/item/device/communicator))
|
||||
var/obj/item/device/communicator/comm = candidate
|
||||
comm.voice_invites -= src
|
||||
|
||||
voice_requests -= candidate
|
||||
|
||||
//Search for holder of our device.
|
||||
var/mob/living/us = null
|
||||
if(loc && isliving(loc))
|
||||
us = loc
|
||||
|
||||
if(us)
|
||||
to_chat(us, "<span class='notice'>\icon[src] Declined request.</span>")
|
||||
|
||||
// Proc: see_emote()
|
||||
// Parameters: 2 (M - the mob the emote originated from, text - the emote's contents)
|
||||
// Description: Relays the emote to all linked communicators.
|
||||
/obj/item/device/communicator/see_emote(mob/living/M, text)
|
||||
var/rendered = "\icon[src] <span class='message'>[text]</span>"
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
var/turf/T = get_turf(comm)
|
||||
if(!T) return
|
||||
var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0) //Range of 3 since it's a tiny video display
|
||||
var/list/mobs_to_relay = in_range["mobs"]
|
||||
|
||||
for(var/mob/mob in mobs_to_relay) //We can't use visible_message(), or else we will get an infinite loop if two communicators hear each other.
|
||||
var/dst = get_dist(get_turf(mob),get_turf(comm))
|
||||
if(dst <= video_range)
|
||||
mob.show_message(rendered)
|
||||
else
|
||||
to_chat(mob, "You can barely see some movement on \the [src]'s display.")
|
||||
|
||||
..()
|
||||
|
||||
// Proc: hear_talk()
|
||||
// Parameters: 4 (M - the mob the speech originated from, text - what is being said, verb - the word used to describe how text is being said, speaking - language
|
||||
// being used)
|
||||
// Description: Relays the speech to all linked communicators.
|
||||
/obj/item/device/communicator/hear_talk(mob/living/M, text, verb, datum/language/speaking)
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
|
||||
var/turf/T = get_turf(comm)
|
||||
if(!T) return
|
||||
var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
|
||||
var/list/mobs_to_relay = in_range["mobs"]
|
||||
|
||||
for(var/mob/mob in mobs_to_relay)
|
||||
//Can whoever is hearing us understand?
|
||||
if(!mob.say_understands(M, speaking))
|
||||
if(speaking)
|
||||
text = speaking.scramble(text)
|
||||
else
|
||||
text = stars(text)
|
||||
var/name_used = M.GetVoice()
|
||||
var/rendered = null
|
||||
if(speaking) //Language being used
|
||||
rendered = "<span class='game say'>\icon[src] <span class='name'>[name_used]</span> [speaking.format_message(text, verb)]</span>"
|
||||
else
|
||||
rendered = "<span class='game say'>\icon[src] <span class='name'>[name_used]</span> [verb], <span class='message'>\"[text]\"</span></span>"
|
||||
mob.show_message(rendered, 2)
|
||||
|
||||
// Proc: show_message()
|
||||
// Parameters: 4 (msg - the message, type - number to determine if message is visible or audible, alt - unknown, alt_type - unknown)
|
||||
// Description: Relays the message to all linked communicators.
|
||||
/obj/item/device/communicator/show_message(msg, type, alt, alt_type)
|
||||
var/rendered = "\icon[src] <span class='message'>[msg]</span>"
|
||||
for(var/obj/item/device/communicator/comm in communicating)
|
||||
var/turf/T = get_turf(comm)
|
||||
if(!T) return
|
||||
var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
|
||||
var/list/mobs_to_relay = in_range["mobs"]
|
||||
|
||||
for(var/mob/mob in mobs_to_relay)
|
||||
mob.show_message(rendered)
|
||||
..()
|
||||
|
||||
// Verb: join_as_voice()
|
||||
// Parameters: None
|
||||
// Description: Allows ghosts to call communicators, if they meet all the requirements.
|
||||
/mob/observer/dead/verb/join_as_voice()
|
||||
set category = "Ghost"
|
||||
set name = "Call Communicator"
|
||||
set desc = "If there is a communicator available, send a request to speak through it. This will reset your respawn timer, if someone picks up."
|
||||
|
||||
if(ticker.current_state < GAME_STATE_PLAYING)
|
||||
to_chat(src, "<span class='danger'>The game hasn't started yet!</span>")
|
||||
return
|
||||
|
||||
if (!src.stat)
|
||||
return
|
||||
|
||||
if (usr != src)
|
||||
return //something is terribly wrong
|
||||
|
||||
var/confirm = alert(src, "Would you like to talk as [src.client.prefs.real_name], over a communicator? \
|
||||
This will reset your respawn timer, if someone answers.", "Join as Voice?", "Yes","No")
|
||||
if(confirm == "No")
|
||||
return
|
||||
|
||||
for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling.
|
||||
if(src.client.prefs.real_name == L.real_name)
|
||||
to_chat(src, "<span class='danger'>Your identity is already present in the game world. Please load in a different character first.</span>")
|
||||
return
|
||||
|
||||
var/obj/machinery/exonet_node/E = get_exonet_node()
|
||||
if(!E || !E.on || !E.allow_external_communicators)
|
||||
to_chat(src, "<span class='danger'>The Exonet node at telecommunications is down at the moment, or is actively blocking you, \
|
||||
so your call can't go through.</span>")
|
||||
return
|
||||
|
||||
var/list/choices = list()
|
||||
for(var/obj/item/device/communicator/comm in all_communicators)
|
||||
if(!comm.network_visibility || !comm.exonet || !comm.exonet.address)
|
||||
continue
|
||||
choices.Add(comm)
|
||||
|
||||
if(!choices.len)
|
||||
to_chat(src , "<span class='danger'>There are no available communicators, sorry.</span>")
|
||||
return
|
||||
|
||||
var/choice = input(src,"Send a voice request to whom?") as null|anything in choices
|
||||
if(choice)
|
||||
var/obj/item/device/communicator/chosen_communicator = choice
|
||||
var/mob/observer/dead/O = src
|
||||
if(O.exonet)
|
||||
O.exonet.send_message(chosen_communicator.exonet.address, "voice")
|
||||
|
||||
to_chat(src, "A communications request has been sent to [chosen_communicator]. Now you need to wait until someone answers.")
|
||||
|
||||
// Proc: connect_video()
|
||||
// Parameters: user - the mob doing the viewing of video, comm - the communicator at the far end
|
||||
// Description: Sets up a videocall and puts the first view into it using watch_video, and updates the icon
|
||||
/obj/item/device/communicator/proc/connect_video(mob/user,obj/item/device/communicator/comm)
|
||||
if((!user) || (!comm) || user.stat) return //KO or dead, or already in a video
|
||||
|
||||
if(video_source) //Already in a video
|
||||
to_chat(user, "<span class='danger'>You are already connected to a video call!</span>")
|
||||
|
||||
if(user.blinded) //User is blinded
|
||||
to_chat(user, "<span class='danger'>You cannot see well enough to do that!</span>")
|
||||
|
||||
if(!(src in comm.communicating) || !comm.camera) //You called someone with a broken communicator or one that's fake or yourself or something
|
||||
to_chat(user, "<span class='danger'>\icon[src]ERROR: Video failed. Either bandwidth is too low, or the other communicator is malfunctioning.</span>")
|
||||
|
||||
to_chat(user, "<span class='notice'>\icon[src] Attempting to start video over existing call.</span>")
|
||||
sleep(30)
|
||||
to_chat(user, "<span class='notice'>\icon[src] Please wait...</span>")
|
||||
|
||||
video_source = comm.camera
|
||||
comm.visible_message("<span class='danger'>\icon[src] New video connection from [comm].</span>")
|
||||
watch_video(user)
|
||||
update_icon()
|
||||
|
||||
// Proc: watch_video()
|
||||
// Parameters: user - the mob doing the viewing of video
|
||||
// Description: Moves a mob's eye to the far end for the duration of viewing the far end
|
||||
/obj/item/device/communicator/proc/watch_video(mob/user)
|
||||
if(!Adjacent(user) || !video_source) return
|
||||
user.set_machine(video_source)
|
||||
user.reset_view(video_source)
|
||||
to_chat(user,"<span class='notice'>Now viewing video session. To leave camera view, close the communicator window OR: OOC -> Cancel Camera View</span>")
|
||||
to_chat(user,"<span class='notice'>To return to an active video session, use the communicator in your hand.</span>")
|
||||
spawn(0)
|
||||
while(user.machine == video_source && Adjacent(user))
|
||||
var/turf/T = get_turf(video_source)
|
||||
if(!T || !is_on_same_plane_or_station(T.z, user.z) || !video_source.can_use())
|
||||
user << "<span class='warning'>The screen bursts into static, then goes black.</span>"
|
||||
video_cleanup(user)
|
||||
return
|
||||
sleep(10)
|
||||
|
||||
video_cleanup(user)
|
||||
|
||||
// Proc: video_cleanup()
|
||||
// Parameters: user - the mob who doesn't want to see video anymore
|
||||
// Description: Cleans up mob's client when they stop watching a video
|
||||
/obj/item/device/communicator/proc/video_cleanup(mob/user)
|
||||
if(!user) return
|
||||
|
||||
user.reset_view(null)
|
||||
user.unset_machine()
|
||||
|
||||
// Proc: end_video()
|
||||
// Parameters: reason - the text reason to print for why it ended
|
||||
// Description: Ends the video call by clearing video_source
|
||||
/obj/item/device/communicator/proc/end_video(var/reason)
|
||||
video_source = null
|
||||
|
||||
. = "<span class='danger'>\icon[src] [reason ? reason : "Video session ended"].</span>"
|
||||
|
||||
visible_message(.)
|
||||
update_icon()
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
|
||||
user.visible_message("<span class='notice'>\The [user] directs [src] to [M]'s eyes.</span>", \
|
||||
"<span class='notice'>You direct [src] to [M]'s eyes.</span>")
|
||||
if(H == user) //can't look into your own eyes buster
|
||||
if(H != user) //can't look into your own eyes buster
|
||||
if(M.stat == DEAD || M.blinded) //mob is dead or fully blind
|
||||
user << "<span class='warning'>\The [M]'s pupils do not react to the light!</span>"
|
||||
return
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
/obj/item/device/powersink/Destroy()
|
||||
processing_objects.Remove(src)
|
||||
processing_power_items.Remove(src)
|
||||
STOP_PROCESSING_POWER_OBJECT(src)
|
||||
..()
|
||||
|
||||
/obj/item/device/powersink/attackby(var/obj/item/I, var/mob/user)
|
||||
@@ -50,7 +50,7 @@
|
||||
else
|
||||
if (mode == 2)
|
||||
processing_objects.Remove(src) // Now the power sink actually stops draining the station's power if you unhook it. --NeoFite
|
||||
processing_power_items.Remove(src)
|
||||
STOP_PROCESSING_POWER_OBJECT(src)
|
||||
anchored = 0
|
||||
mode = 0
|
||||
src.visible_message("<span class='notice'>[user] detaches [src] from the cable!</span>")
|
||||
@@ -74,14 +74,14 @@
|
||||
mode = 2
|
||||
icon_state = "powersink1"
|
||||
processing_objects.Add(src)
|
||||
processing_power_items.Add(src)
|
||||
START_PROCESSING_POWER_OBJECT(src)
|
||||
if(2) //This switch option wasn't originally included. It exists now. --NeoFite
|
||||
src.visible_message("<span class='notice'>[user] deactivates [src]!</span>")
|
||||
mode = 1
|
||||
set_light(0)
|
||||
icon_state = "powersink0"
|
||||
processing_objects.Remove(src)
|
||||
processing_power_items.Remove(src)
|
||||
STOP_PROCESSING_POWER_OBJECT(src)
|
||||
|
||||
/obj/item/device/powersink/pwr_drain()
|
||||
if(!attached)
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
/obj/item/weapon/implant/Destroy()
|
||||
if(part)
|
||||
part.implants.Remove(src)
|
||||
part = null
|
||||
return ..()
|
||||
|
||||
/obj/item/weapon/implant/attackby(obj/item/I, mob/user)
|
||||
|
||||
@@ -100,3 +100,4 @@
|
||||
icon_state = "survivalknife"
|
||||
item_state = "knife"
|
||||
applies_material_colour = FALSE
|
||||
should_cleave = FALSE
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
origin_tech = list(TECH_MATERIAL = 2, TECH_COMBAT = 1)
|
||||
attack_verb = list("chopped", "torn", "cut")
|
||||
applies_material_colour = 0
|
||||
var/should_cleave = TRUE // Because knives inherit from hatchets. For some reason.
|
||||
|
||||
// This cannot go into afterattack since some mobs delete themselves upon dying.
|
||||
/obj/item/weapon/material/hatchet/pre_attack(var/mob/living/target, var/mob/living/user)
|
||||
if(should_cleave && istype(target))
|
||||
cleave(user, target)
|
||||
..()
|
||||
|
||||
/obj/item/weapon/material/hatchet/unathiknife
|
||||
name = "duelling knife"
|
||||
@@ -39,6 +46,7 @@
|
||||
icon = 'icons/obj/weapons.dmi'
|
||||
icon_state = "unathiknife"
|
||||
attack_verb = list("ripped", "torn", "cut")
|
||||
should_cleave = FALSE
|
||||
var hits = 0
|
||||
|
||||
/obj/item/weapon/material/hatchet/unathiknife/attack(mob/M as mob, mob/user as mob)
|
||||
@@ -56,6 +64,7 @@
|
||||
hits = initial(hits)
|
||||
..()
|
||||
|
||||
// These probably shouldn't inherit from hatchets.
|
||||
/obj/item/weapon/material/hatchet/tacknife
|
||||
name = "tactical knife"
|
||||
desc = "You'd be killing loads of people if this was Medal of Valor: Heroes of Space."
|
||||
@@ -64,6 +73,7 @@
|
||||
item_state = "knife"
|
||||
attack_verb = list("stabbed", "chopped", "cut")
|
||||
applies_material_colour = 1
|
||||
should_cleave = FALSE
|
||||
|
||||
/obj/item/weapon/material/hatchet/tacknife/combatknife
|
||||
name = "combat knife"
|
||||
@@ -75,6 +85,7 @@
|
||||
thrown_force_divisor = 1.75 // 20 with weight 20 (steel)
|
||||
attack_verb = list("sliced", "stabbed", "chopped", "cut")
|
||||
applies_material_colour = 1
|
||||
should_cleave = FALSE
|
||||
|
||||
/obj/item/weapon/material/minihoe // -- Numbers
|
||||
name = "mini hoe"
|
||||
|
||||
@@ -97,6 +97,12 @@
|
||||
icon_state = initial(icon_state)
|
||||
to_chat(user, "<span class='notice'>\The [src] is de-energised. It's just a regular axe now.</span>")
|
||||
|
||||
// This cannot go into afterattack since some mobs delete themselves upon dying.
|
||||
/obj/item/weapon/melee/energy/axe/pre_attack(var/mob/living/target, var/mob/living/user)
|
||||
if(istype(target))
|
||||
cleave(user, target)
|
||||
..()
|
||||
|
||||
/obj/item/weapon/melee/energy/axe/suicide_act(mob/user)
|
||||
visible_message("<span class='warning'>\The [user] swings \the [src] towards \his head! It looks like \he's trying to commit suicide.</span>")
|
||||
return (BRUTELOSS|FIRELOSS)
|
||||
|
||||
@@ -592,8 +592,9 @@
|
||||
|
||||
/obj/item/weapon/storage/attack_self(mob/user as mob)
|
||||
if((user.get_active_hand() == src) || (isrobot(user)) && allow_quick_empty)
|
||||
src.quick_empty()
|
||||
return 1 // Is this return even needed?
|
||||
if(src.verbs.Find(/obj/item/weapon/storage/verb/quick_empty))
|
||||
src.quick_empty()
|
||||
return 1
|
||||
|
||||
//Returns the storage depth of an atom. This is the number of storage items the atom is contained in before reaching toplevel (the area).
|
||||
//Returns -1 if the atom was not found on container.
|
||||
|
||||
@@ -67,10 +67,10 @@ var/list/global/tank_gauge_cache = list()
|
||||
return
|
||||
|
||||
/obj/item/weapon/tank/Destroy()
|
||||
qdel(air_contents)
|
||||
qdel_null(air_contents)
|
||||
|
||||
processing_objects.Remove(src)
|
||||
qdel(src.proxyassembly)
|
||||
qdel_null(src.proxyassembly)
|
||||
|
||||
if(istype(loc, /obj/item/device/transfer_valve))
|
||||
var/obj/item/device/transfer_valve/TTV = loc
|
||||
@@ -609,6 +609,10 @@ var/list/global/tank_gauge_cache = list()
|
||||
/obj/item/device/tankassemblyproxy/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here.
|
||||
tank.ignite() //boom (or not boom if you made shijwtty mix)
|
||||
|
||||
/obj/item/device/tankassemblyproxy/Destroy()
|
||||
. = ..()
|
||||
tank = null
|
||||
assembly = null
|
||||
|
||||
/obj/item/weapon/tank/proc/assemble_bomb(W,user) //Bomb assembly proc. This turns assembly+tank into a bomb
|
||||
var/obj/item/device/assembly_holder/S = W
|
||||
|
||||
@@ -133,7 +133,7 @@ Frequency:
|
||||
|
||||
/obj/item/weapon/hand_tele/attack_self(mob/user as mob)
|
||||
var/turf/current_location = get_turf(user)//What turf is the user on?
|
||||
if(!current_location||current_location.z==2||current_location.z>=7 || current_location.block_tele)//If turf was not found or they're on z level 2 or >7 which does not currently exist.
|
||||
if(!current_location || current_location.z in using_map.admin_levels || current_location.block_tele)//If turf was not found or they're on z level 2 or >7 which does not currently exist.
|
||||
user << "<span class='notice'>\The [src] is malfunctioning.</span>"
|
||||
return
|
||||
var/list/L = list( )
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
/obj/item/weapon/proc/cleave(var/mob/living/user, var/mob/living/target)
|
||||
if(cleaving)
|
||||
return // We're busy.
|
||||
if(get_turf(user) == get_turf(target))
|
||||
return // Otherwise we would hit all eight surrounding tiles.
|
||||
cleaving = TRUE
|
||||
var/hit_mobs = 0
|
||||
for(var/mob/living/simple_animal/SA in orange(get_turf(target), 1))
|
||||
@@ -35,6 +37,13 @@
|
||||
if(resolve_attackby(SA, user)) // Hit them with the weapon. This won't cause recursive cleaving due to the cleaving variable being set to true.
|
||||
hit_mobs++
|
||||
|
||||
cleave_visual(user, target)
|
||||
|
||||
if(hit_mobs)
|
||||
to_chat(user, "<span class='danger'>You used \the [src] to attack [hit_mobs] other thing\s!</span>")
|
||||
cleaving = FALSE // We're done now.
|
||||
cleaving = FALSE // We're done now.
|
||||
|
||||
// This is purely the visual effect of cleaving.
|
||||
/obj/item/weapon/proc/cleave_visual(var/mob/living/user, var/mob/living/target)
|
||||
var/obj/effect/temporary_effect/cleave_attack/E = new(get_turf(src))
|
||||
E.dir = get_dir(user, target)
|
||||
@@ -181,6 +181,9 @@
|
||||
icon_state = "alienplating"
|
||||
block_tele = TRUE
|
||||
|
||||
/turf/simulated/shuttle/floor/alienplating/external // For the outer rim of the UFO, to avoid active edges.
|
||||
// The actual temperature adjustment is defined if the SC or other future map is compiled.
|
||||
|
||||
/turf/simulated/shuttle/plating
|
||||
name = "plating"
|
||||
icon = 'icons/turf/floors.dmi'
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
// Items that ask to be called every cycle.
|
||||
var/global/datum/datacore/data_core = null
|
||||
var/global/list/all_areas = list()
|
||||
var/global/list/machines = list()
|
||||
var/global/list/machines = list() // TODO - Move into SSmachines
|
||||
var/global/list/processing_objects = list()
|
||||
var/global/list/processing_power_items = list()
|
||||
var/global/list/processing_power_items = list() // TODO - Move into SSmachines
|
||||
var/global/list/active_diseases = list()
|
||||
var/global/list/med_hud_users = list() // List of all entities using a medical HUD.
|
||||
var/global/list/sec_hud_users = list() // List of all entities using a security HUD.
|
||||
@@ -107,7 +107,7 @@ var/list/IClog = list()
|
||||
var/list/OOClog = list()
|
||||
var/list/adminlog = list()
|
||||
|
||||
var/list/powernets = list()
|
||||
var/list/powernets = list() // TODO - Move into SSmachines
|
||||
|
||||
var/Debug2 = 0
|
||||
var/datum/debug/debugobj
|
||||
|
||||
@@ -192,6 +192,7 @@ var/list/admin_verbs_debug = list(
|
||||
/client/proc/cmd_admin_delete,
|
||||
/client/proc/cmd_debug_del_all,
|
||||
/client/proc/cmd_debug_tog_aliens,
|
||||
/client/proc/cmd_display_del_log,
|
||||
/client/proc/air_report,
|
||||
/client/proc/reload_admins,
|
||||
/client/proc/reload_eventMs,
|
||||
@@ -309,6 +310,7 @@ var/list/admin_verbs_hideable = list(
|
||||
/client/proc/cmd_debug_using_map,
|
||||
/client/proc/cmd_debug_del_all,
|
||||
/client/proc/cmd_debug_tog_aliens,
|
||||
/client/proc/cmd_display_del_log,
|
||||
/client/proc/air_report,
|
||||
/client/proc/enable_debug_verbs,
|
||||
/client/proc/roll_dices,
|
||||
|
||||
@@ -277,9 +277,9 @@
|
||||
/client/proc/cmd_debug_make_powernets()
|
||||
set category = "Debug"
|
||||
set name = "Make Powernets"
|
||||
makepowernets()
|
||||
log_admin("[key_name(src)] has remade the powernet. makepowernets() called.")
|
||||
message_admins("[key_name_admin(src)] has remade the powernets. makepowernets() called.", 0)
|
||||
SSmachines.makepowernets()
|
||||
log_admin("[key_name(src)] has remade the powernet. SSmachines.makepowernets() called.")
|
||||
message_admins("[key_name_admin(src)] has remade the powernets. SSmachines.makepowernets() called.", 0)
|
||||
feedback_add_details("admin_verb","MPWN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/cmd_debug_tog_aliens()
|
||||
@@ -291,6 +291,36 @@
|
||||
message_admins("[key_name_admin(src)] has turned aliens [config.aliens_allowed ? "on" : "off"].", 0)
|
||||
feedback_add_details("admin_verb","TAL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/cmd_display_del_log()
|
||||
set category = "Debug"
|
||||
set name = "Display del() Log"
|
||||
set desc = "Display del's log of everything that's passed through it."
|
||||
|
||||
if(!check_rights(R_DEBUG)) return
|
||||
var/list/dellog = list("<B>List of things that have gone through qdel this round</B><BR><BR><ol>")
|
||||
sortTim(SSgarbage.items, cmp=/proc/cmp_qdel_item_time, associative = TRUE)
|
||||
for(var/path in SSgarbage.items)
|
||||
var/datum/qdel_item/I = SSgarbage.items[path]
|
||||
dellog += "<li><u>[path]</u><ul>"
|
||||
if (I.failures)
|
||||
dellog += "<li>Failures: [I.failures]</li>"
|
||||
dellog += "<li>qdel() Count: [I.qdels]</li>"
|
||||
dellog += "<li>Destroy() Cost: [I.destroy_time]ms</li>"
|
||||
if (I.hard_deletes)
|
||||
dellog += "<li>Total Hard Deletes [I.hard_deletes]</li>"
|
||||
dellog += "<li>Time Spent Hard Deleting: [I.hard_delete_time]ms</li>"
|
||||
if (I.slept_destroy)
|
||||
dellog += "<li>Sleeps: [I.slept_destroy]</li>"
|
||||
if (I.no_respect_force)
|
||||
dellog += "<li>Ignored force: [I.no_respect_force]</li>"
|
||||
if (I.no_hint)
|
||||
dellog += "<li>No hint: [I.no_hint]</li>"
|
||||
dellog += "</ul></li>"
|
||||
|
||||
dellog += "</ol>"
|
||||
|
||||
usr << browse(dellog.Join(), "window=dellog")
|
||||
|
||||
/client/proc/cmd_admin_grantfullaccess(var/mob/M in mob_list)
|
||||
set category = "Admin"
|
||||
set name = "Grant Full Access"
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
var/related_accounts_ip = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip
|
||||
var/related_accounts_cid = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id
|
||||
|
||||
preload_rsc = 0 // This is 0 so we can set it to an URL once the player logs in and have them download the resources from a different server.
|
||||
preload_rsc = PRELOAD_RSC
|
||||
|
||||
var/global/obj/screen/click_catcher/void
|
||||
|
||||
|
||||
|
||||
@@ -105,11 +105,6 @@
|
||||
del(src)
|
||||
return
|
||||
|
||||
// Change the way they should download resources.
|
||||
if(config.resource_urls)
|
||||
src.preload_rsc = pick(config.resource_urls)
|
||||
else src.preload_rsc = 1 // If config.resource_urls is not set, preload like normal.
|
||||
|
||||
src << "<font color='red'>If the title screen is black, resources are still downloading. Please be patient until the title screen appears.</font>"
|
||||
|
||||
|
||||
@@ -296,8 +291,7 @@
|
||||
if (holder)
|
||||
sleep(1)
|
||||
else
|
||||
sleep(5)
|
||||
stoplag()
|
||||
stoplag(5)
|
||||
|
||||
/client/proc/last_activity_seconds()
|
||||
return inactivity / 10
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
if(alt_title && !(alt_title in job.alt_titles))
|
||||
pref.player_alt_titles -= job.title
|
||||
|
||||
/datum/category_item/player_setup_item/occupation/content(mob/user, limit = 17, list/splitJobs = list("Chief Medical Officer"))
|
||||
/datum/category_item/player_setup_item/occupation/content(mob/user, limit = 18, list/splitJobs = list("Chief Medical Officer"))
|
||||
if(!job_master)
|
||||
return
|
||||
|
||||
@@ -70,13 +70,14 @@
|
||||
if (!job_master) return
|
||||
for(var/datum/job/job in job_master.occupations)
|
||||
if(job.latejoin_only) continue //VOREStation Code
|
||||
index += 1
|
||||
if((index >= limit) || (job.title in splitJobs))
|
||||
if((++index >= limit) || (job.title in splitJobs))
|
||||
/*******
|
||||
if((index < limit) && (lastJob != null))
|
||||
//If the cells were broken up by a job in the splitJob list then it will fill in the rest of the cells with
|
||||
//the last job's selection color. Creating a rather nice effect.
|
||||
for(var/i = 0, i < (limit - index), i += 1)
|
||||
. += "<tr bgcolor='[lastJob.selection_color]'><td width='60%' align='right'><a> </a></td><td><a> </a></td></tr>"
|
||||
//the last job's selection color and blank buttons that do nothing. Creating a rather nice effect.
|
||||
for(var/i = 0, i < (limit - index), i++)
|
||||
. += "<tr bgcolor='[lastJob.selection_color]'><td width='60%' align='right'>//> </a></td><td><a> </a></td></tr>"
|
||||
*******/
|
||||
. += "</table></td><td width='20%'><table width='100%' cellpadding='1' cellspacing='0'>"
|
||||
index = 0
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
species_restricted = list("exclude","Diona")
|
||||
preserve_item = 1
|
||||
phoronproof = 1
|
||||
flash_protection = FLASH_PROTECTION_NONE
|
||||
flash_protection = FLASH_PROTECTION_MAJOR
|
||||
|
||||
var/obj/machinery/camera/camera
|
||||
var/list/camera_networks
|
||||
|
||||
@@ -53,11 +53,9 @@
|
||||
processing_objects |= src
|
||||
|
||||
/obj/item/device/electronic_assembly/Destroy()
|
||||
battery = null
|
||||
battery = null // It will be qdel'd by ..() if still in our contents
|
||||
processing_objects -= src
|
||||
for(var/atom/movable/AM in contents)
|
||||
qdel(AM)
|
||||
..()
|
||||
return ..()
|
||||
|
||||
/obj/item/device/electronic_assembly/process()
|
||||
handle_idle_power()
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
power_draw_per_use = 50 // The targeting mechanism uses this. The actual gun uses its own cell for firing if it's an energy weapon.
|
||||
|
||||
/obj/item/integrated_circuit/manipulation/weapon_firing/Destroy()
|
||||
qdel(installed_gun)
|
||||
..()
|
||||
installed_gun = null // It will be qdel'd by ..() if still in our contents
|
||||
return ..()
|
||||
|
||||
/obj/item/integrated_circuit/manipulation/weapon_firing/attackby(var/obj/O, var/mob/user)
|
||||
if(istype(O, /obj/item/weapon/gun))
|
||||
|
||||
@@ -98,7 +98,7 @@ var/list/global/map_templates = list()
|
||||
admin_notice("<span class='danger'>[i] pipe\s initialized.</span>", R_DEBUG)
|
||||
|
||||
admin_notice("<span class='danger'>Rebuilding powernets due to submap creation.</span>", R_DEBUG)
|
||||
makepowernets()
|
||||
SSmachines.makepowernets()
|
||||
|
||||
admin_notice("<span class='danger'>Submap initializations finished.</span>", R_DEBUG)
|
||||
|
||||
|
||||
@@ -726,8 +726,6 @@
|
||||
add_clothing_protection(glasses)
|
||||
if(istype(src.wear_mask, /obj/item/clothing/mask))
|
||||
add_clothing_protection(wear_mask)
|
||||
if(istype(back,/obj/item/weapon/rig))
|
||||
add_clothing_protection(back)
|
||||
|
||||
return flash_protection
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
icon_living = "alienh_running"
|
||||
icon_dead = "alien_l"
|
||||
icon_gib = "syndicate_gib"
|
||||
icon_rest = "alienh_sleep"
|
||||
|
||||
faction = "xeno"
|
||||
intelligence_level = SA_HUMANOID
|
||||
@@ -50,6 +51,7 @@
|
||||
icon_state = "aliend_running"
|
||||
icon_living = "aliend_running"
|
||||
icon_dead = "aliend_l"
|
||||
icon_rest = "aliend_sleep"
|
||||
health = 60
|
||||
melee_damage_lower = 15
|
||||
melee_damage_upper = 15
|
||||
@@ -59,6 +61,7 @@
|
||||
icon_state = "aliens_running"
|
||||
icon_living = "aliens_running"
|
||||
icon_dead = "aliens_l"
|
||||
icon_rest = "aliens_sleep"
|
||||
health = 120
|
||||
melee_damage_lower = 15
|
||||
melee_damage_upper = 15
|
||||
@@ -72,6 +75,7 @@
|
||||
icon_state = "prat_s"
|
||||
icon_living = "prat_s"
|
||||
icon_dead = "prat_dead"
|
||||
icon_rest = "prat_sleep"
|
||||
move_to_delay = 5
|
||||
maxHealth = 200
|
||||
health = 200
|
||||
@@ -85,6 +89,7 @@
|
||||
icon_state = "alienq_running"
|
||||
icon_living = "alienq_running"
|
||||
icon_dead = "alienq_l"
|
||||
icon_rest = "alienq_sleep"
|
||||
health = 250
|
||||
maxHealth = 250
|
||||
melee_damage_lower = 15
|
||||
@@ -102,6 +107,7 @@
|
||||
icon_state = "queen_s"
|
||||
icon_living = "queen_s"
|
||||
icon_dead = "queen_dead"
|
||||
icon_rest = "queen_sleep"
|
||||
move_to_delay = 4
|
||||
maxHealth = 400
|
||||
health = 400
|
||||
@@ -117,6 +123,7 @@
|
||||
icon_state = "empress_s"
|
||||
icon_living = "empress_s"
|
||||
icon_dead = "empress_dead"
|
||||
icon_rest = "empress_rest"
|
||||
maxHealth = 600
|
||||
health = 600
|
||||
meat_amount = 10
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
item_state = "cat2"
|
||||
icon_living = "cat2"
|
||||
icon_dead = "cat2_dead"
|
||||
icon_rest = "cat2_rest"
|
||||
|
||||
hostile = 1 //To mice, anyway.
|
||||
investigates = 1
|
||||
@@ -160,6 +161,7 @@
|
||||
item_state = "cat"
|
||||
icon_living = "cat"
|
||||
icon_dead = "cat_dead"
|
||||
icon_rest = "cat_rest"
|
||||
befriend_job = "Chief Medical Officer"
|
||||
|
||||
/mob/living/simple_animal/cat/kitten
|
||||
@@ -186,6 +188,7 @@
|
||||
item_state = "cat3"
|
||||
icon_living = "cat3"
|
||||
icon_dead = "cat3_dead"
|
||||
icon_rest = "cat3_rest"
|
||||
holder_type = /obj/item/weapon/holder/cat/fluff/bones
|
||||
var/friend_name = "Erstatz Vryroxes"
|
||||
|
||||
|
||||
@@ -1172,7 +1172,7 @@
|
||||
//They ran away!
|
||||
else
|
||||
ai_log("AttackTarget() out of range!",3)
|
||||
sleep(1) // Unfortunately this is needed to protect from ClosestDistance() sometimes not updating fast enough to prevent an infinite loop.
|
||||
stoplag(1) // Unfortunately this is needed to protect from ClosestDistance() sometimes not updating fast enough to prevent an infinite loop.
|
||||
handle_stance(STANCE_ATTACK)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -85,10 +85,12 @@
|
||||
/mob/living/simple_animal/slime/purple
|
||||
)
|
||||
var/type_on_death = null // Set this if you want dying slimes to split into a specific type and not their type.
|
||||
var/rainbow_core_candidate = TRUE // If false, rainbow cores cannot make this type randomly.
|
||||
|
||||
var/reagent_injected = null // Some slimes inject reagents on attack. This tells the game what reagent to use.
|
||||
var/injection_amount = 5 // This determines how much.
|
||||
|
||||
|
||||
can_enter_vent_with = list(
|
||||
/obj/item/clothing/head,
|
||||
)
|
||||
|
||||
@@ -209,9 +209,9 @@
|
||||
/mob/living/simple_animal/slime/dark_blue/get_cold_protection()
|
||||
return 1 // This slime is immune to cold.
|
||||
|
||||
// Surfave variant
|
||||
/mob/living/simple_animal/slime/dark_blue/wild
|
||||
name = "wild slime"
|
||||
// Surface variant
|
||||
/mob/living/simple_animal/slime/dark_blue/feral
|
||||
name = "feral slime"
|
||||
desc = "The result of slimes escaping containment from some xenobiology lab. The slime makes other entities near it feel much colder, \
|
||||
and it is more resilient to the cold. These qualities have made this color of slime able to thrive on a harsh, cold world and is able to rival \
|
||||
the ferocity of other apex predators in this region of Sif. As such, it is a very invasive species."
|
||||
@@ -220,13 +220,14 @@
|
||||
icon_scale = 2
|
||||
optimal_combat = TRUE // Gotta be sharp to survive out there.
|
||||
rabid = TRUE
|
||||
rainbow_core_candidate = FALSE
|
||||
cores = 6
|
||||
maxHealth = 150 // Base health
|
||||
maxHealth = 150
|
||||
maxHealth_adult = 250
|
||||
type_on_death = /mob/living/simple_animal/slime/dark_blue // Otherwise infinite slimes might occur.
|
||||
pixel_y = -10 // Since the base sprite isn't centered properly, the pixel auto-adjustment needs some help.
|
||||
|
||||
/mob/living/simple_animal/slime/dark_blue/wild/New()
|
||||
/mob/living/simple_animal/slime/dark_blue/feral/New()
|
||||
..()
|
||||
make_adult()
|
||||
|
||||
@@ -735,6 +736,7 @@
|
||||
/mob/living/simple_animal/slime/rainbow/kendrick
|
||||
name = "Kendrick"
|
||||
desc = "The Research Director's pet slime. It shifts colors constantly."
|
||||
rainbow_core_candidate = FALSE
|
||||
|
||||
/mob/living/simple_animal/slime/rainbow/kendrick/New()
|
||||
pacify()
|
||||
|
||||
@@ -400,7 +400,7 @@ proc/is_blind(A)
|
||||
return // Can't talk in deadchat if you can't see it.
|
||||
|
||||
for(var/mob/M in player_list)
|
||||
if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || M.client.holder) && M.is_preference_enabled(/datum/client_preference/show_dsay)) //VOREStation Edit - Why'd you make it so admins can't see deadchat?
|
||||
if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights)) && M.is_preference_enabled(/datum/client_preference/show_dsay))
|
||||
var/follow
|
||||
var/lname
|
||||
if(M.forbid_seeing_deadchat && !M.client.holder)
|
||||
@@ -615,4 +615,4 @@ var/list/global/organ_rel_size = list(
|
||||
)
|
||||
|
||||
/mob/proc/flash_eyes(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = FALSE, visual = FALSE, type = /obj/screen/fullscreen/flash)
|
||||
return
|
||||
return
|
||||
|
||||
270
code/modules/multiz/hoist.dm
Normal file
@@ -0,0 +1,270 @@
|
||||
///////////////////////////
|
||||
// Dost thou even hoist? //
|
||||
///////////////////////////
|
||||
|
||||
#define NORMAL_LAYER 3
|
||||
|
||||
/obj/item/hoist_kit
|
||||
name = "hoist kit"
|
||||
desc = "A setup kit for a hoist that can be used to lift things. The hoist will deploy in the direction you're facing."
|
||||
icon = 'icons/obj/hoists.dmi'
|
||||
icon_state = "hoist_case"
|
||||
|
||||
/obj/item/hoist_kit/attack_self(mob/user)
|
||||
new /obj/structure/hoist (get_turf(user), user.dir)
|
||||
user.visible_message(span("warning", "[user] deploys the hoist kit!"), span("notice", "You deploy the hoist kit!"), span("notice", "You hear the sound of parts snapping into place."))
|
||||
qdel(src)
|
||||
|
||||
/obj/effect/hoist_hook
|
||||
name = "hoist clamp"
|
||||
desc = "A clamp used to lift people or things."
|
||||
icon = 'icons/obj/hoists.dmi'
|
||||
icon_state = "hoist_hook"
|
||||
var/obj/structure/hoist/source_hoist
|
||||
can_buckle = 1
|
||||
anchored = 1
|
||||
description_info = "Click and drag someone (or any object) to this to attach them to the clamp. If you are within reach, when you click and drag this to a turf adjacent to you, it will move the attached object there and release it."
|
||||
|
||||
/obj/effect/hoist_hook/attack_hand(mob/living/user)
|
||||
return // This has to be overridden so that it works properly.
|
||||
|
||||
/obj/effect/hoist_hook/MouseDrop_T(atom/movable/AM,mob/user)
|
||||
if (use_check(user, USE_DISALLOW_SILICONS))
|
||||
return
|
||||
|
||||
if (!AM.simulated || AM.anchored)
|
||||
to_chat(user, span("notice", "You can't do that."))
|
||||
return
|
||||
if (source_hoist.hoistee)
|
||||
to_chat(user, span("notice", "\The [source_hoist.hoistee] is already attached to \the [src]!"))
|
||||
return
|
||||
source_hoist.attach_hoistee(AM)
|
||||
user.visible_message(span("danger", "[user] attaches \the [AM] to \the [src]."), span("danger", "You attach \the [AM] to \the [src]."), span("danger", "You hear something clamp into place."))
|
||||
|
||||
/obj/structure/hoist/proc/attach_hoistee(atom/movable/AM)
|
||||
if (get_turf(AM) != get_turf(source_hook))
|
||||
AM.forceMove(get_turf(source_hook))
|
||||
hoistee = AM
|
||||
if(ismob(AM))
|
||||
source_hook.buckle_mob(AM)
|
||||
AM.anchored = 1 // why isn't this being set by buckle_mob for silicons?
|
||||
source_hook.layer = AM.layer + 0.1
|
||||
|
||||
/obj/effect/hoist_hook/MouseDrop(atom/dest)
|
||||
..()
|
||||
if(!Adjacent(usr) || !dest.Adjacent(usr)) return // carried over from the default proc
|
||||
|
||||
if (!ishuman(usr))
|
||||
return
|
||||
|
||||
if (usr.incapacitated())
|
||||
to_chat(usr, span("notice", "You can't do that while incapacitated."))
|
||||
return
|
||||
|
||||
if (!usr.IsAdvancedToolUser())
|
||||
to_chat(usr, span("notice", "You stare cluelessly at \the [src]."))
|
||||
return
|
||||
|
||||
if (!source_hoist.hoistee)
|
||||
return
|
||||
if (!isturf(dest))
|
||||
return
|
||||
if (!dest.Adjacent(source_hoist.hoistee))
|
||||
return
|
||||
|
||||
source_hoist.check_consistency()
|
||||
|
||||
var/turf/desturf = dest
|
||||
source_hoist.hoistee.forceMove(desturf)
|
||||
usr.visible_message(span("danger", "[usr] detaches \the [source_hoist.hoistee] from the hoist clamp."), span("danger", "You detach \the [source_hoist.hoistee] from the hoist clamp."), span("danger", "You hear something unclamp."))
|
||||
source_hoist.release_hoistee()
|
||||
|
||||
// This will handle mobs unbuckling themselves.
|
||||
/obj/effect/hoist_hook/unbuckle_mob()
|
||||
. = ..()
|
||||
if (. && !QDELETED(source_hoist))
|
||||
var/mob/M = .
|
||||
source_hoist.hoistee = null
|
||||
M.fall()
|
||||
|
||||
/obj/structure/hoist
|
||||
icon = 'icons/obj/hoists.dmi'
|
||||
icon_state = "hoist_base"
|
||||
var/broken = 0
|
||||
density = 1
|
||||
anchored = 1
|
||||
name = "hoist"
|
||||
desc = "A manual hoist, uses a clamp and pulley to hoist things."
|
||||
var/atom/movable/hoistee
|
||||
var/movedir = UP
|
||||
var/obj/effect/hoist_hook/source_hook
|
||||
description_info = "Click this to raise or lower the hoist, or to switch directions if it can't move any further. It can also be collapsed into a hoist kit."
|
||||
|
||||
/obj/structure/hoist/initialize(mapload, ndir)
|
||||
. = ..()
|
||||
dir = ndir
|
||||
var/turf/newloc = get_step(src, dir)
|
||||
source_hook = new(newloc)
|
||||
source_hook.source_hoist = src
|
||||
|
||||
/obj/structure/hoist/Destroy()
|
||||
if(hoistee)
|
||||
release_hoistee()
|
||||
qdel_null(src.source_hook)
|
||||
return ..()
|
||||
|
||||
/obj/effect/hoist_hook/Destroy()
|
||||
source_hoist = null
|
||||
return ..()
|
||||
|
||||
/obj/structure/hoist/proc/check_consistency()
|
||||
if (!hoistee)
|
||||
return
|
||||
if (hoistee.z != source_hook.z)
|
||||
release_hoistee()
|
||||
return
|
||||
|
||||
/obj/structure/hoist/proc/release_hoistee()
|
||||
if(ismob(hoistee))
|
||||
source_hook.unbuckle_mob(hoistee)
|
||||
else
|
||||
hoistee.anchored = 0
|
||||
hoistee = null
|
||||
layer = NORMAL_LAYER
|
||||
|
||||
/obj/structure/hoist/proc/break_hoist()
|
||||
if(broken)
|
||||
return
|
||||
broken = 1
|
||||
desc += " It looks broken, and the clamp has retracted back into the hoist. Seems like you'd have to re-deploy it to get it to work again."
|
||||
if(hoistee)
|
||||
release_hoistee()
|
||||
qdel_null(source_hook)
|
||||
|
||||
/obj/structure/hoist/ex_act(severity)
|
||||
switch(severity)
|
||||
if(1.0)
|
||||
qdel(src)
|
||||
return
|
||||
if(2.0)
|
||||
if(prob(50))
|
||||
qdel(src)
|
||||
else
|
||||
visible_message("\The [src] shakes violently, and neatly collapses as its damage sensors go off.")
|
||||
collapse_kit()
|
||||
return
|
||||
if(3.0)
|
||||
if(prob(50) && !broken)
|
||||
break_hoist()
|
||||
return
|
||||
|
||||
/obj/effect/hoist_hook/ex_act(severity)
|
||||
switch(severity)
|
||||
if(1.0)
|
||||
source_hoist.break_hoist()
|
||||
return
|
||||
if(2.0)
|
||||
if(prob(50))
|
||||
source_hoist.break_hoist()
|
||||
return
|
||||
if(3.0)
|
||||
if(prob(25))
|
||||
source_hoist.break_hoist()
|
||||
return
|
||||
|
||||
|
||||
/obj/structure/hoist/attack_hand(mob/living/user)
|
||||
if (!ishuman(user))
|
||||
return
|
||||
|
||||
if (user.incapacitated())
|
||||
to_chat(user, span("notice", "You can't do that while incapacitated."))
|
||||
return
|
||||
|
||||
if (!user.IsAdvancedToolUser())
|
||||
to_chat(user, span("notice", "You stare cluelessly at \the [src]."))
|
||||
return
|
||||
|
||||
if(broken)
|
||||
to_chat(user, span("warning", "The hoist is broken!"))
|
||||
return
|
||||
var/can = can_move_dir(movedir)
|
||||
var/movtext = movedir == UP ? "raise" : "lower"
|
||||
if (!can) // If you can't...
|
||||
movedir = movedir == UP ? DOWN : UP // switch directions!
|
||||
to_chat(user, span("notice", "You switch the direction of the pulley."))
|
||||
return
|
||||
|
||||
if (!hoistee)
|
||||
user.visible_message(span("notice", "[user] begins to [movtext] the clamp."), span("notice", "You begin to [movtext] the clamp."), span("notice", "You hear the sound of a crank."))
|
||||
move_dir(movedir, 0)
|
||||
return
|
||||
|
||||
check_consistency()
|
||||
|
||||
var/size
|
||||
if (ismob(hoistee))
|
||||
var/mob/M = hoistee
|
||||
size = M.mob_size
|
||||
else if (isobj(hoistee))
|
||||
var/obj/O = hoistee
|
||||
size = O.w_class
|
||||
|
||||
user.visible_message(span("notice", "[user] begins to [movtext] \the [hoistee]!"), span("notice", "You begin to [movtext] \the [hoistee]!"), span("notice", "You hear the sound of a crank."))
|
||||
if (do_after(user, (1 SECONDS) * size / 4, act_target = src))
|
||||
move_dir(movedir, 1)
|
||||
|
||||
/obj/structure/hoist/proc/collapse_kit()
|
||||
new /obj/item/hoist_kit(get_turf(src))
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/hoist/verb/collapse_hoist()
|
||||
set name = "Collapse Hoist"
|
||||
set category = "Object"
|
||||
set src in range(1)
|
||||
|
||||
if (!ishuman(usr))
|
||||
return
|
||||
|
||||
if (isobserver(usr) || usr.incapacitated())
|
||||
return
|
||||
if (!usr.IsAdvancedToolUser()) // thanks nanacode
|
||||
to_chat(usr, span("notice", "You stare cluelessly at \the [src]."))
|
||||
return
|
||||
|
||||
if (hoistee)
|
||||
to_chat(usr, span("notice", "You cannot collapse the hoist with \the [hoistee] attached!"))
|
||||
return
|
||||
collapse_kit()
|
||||
|
||||
/obj/structure/hoist/proc/can_move_dir(direction)
|
||||
var/turf/dest = direction == UP ? GetAbove(source_hook) : GetBelow(source_hook)
|
||||
switch(direction)
|
||||
if (UP)
|
||||
if (!isopenspace(dest)) // can't move into a solid tile
|
||||
return 0
|
||||
if (source_hook in get_step(src, dir)) // you don't get to move above the hoist
|
||||
return 0
|
||||
if (DOWN)
|
||||
if (!isopenspace(get_turf(source_hook))) // can't move down through a solid tile
|
||||
return 0
|
||||
if (!dest) // can't move if there's nothing to move to
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/obj/structure/hoist/proc/move_dir(direction, ishoisting)
|
||||
var/can = can_move_dir(direction)
|
||||
if (!can)
|
||||
return 0
|
||||
var/turf/move_dest = direction == UP ? GetAbove(source_hook) : GetBelow(source_hook)
|
||||
source_hook.forceMove(move_dest)
|
||||
if (!ishoisting)
|
||||
return 1
|
||||
hoistee.hoist_act(move_dest)
|
||||
return 1
|
||||
|
||||
/atom/movable/proc/hoist_act(turf/dest)
|
||||
forceMove(dest)
|
||||
return TRUE
|
||||
|
||||
#undef NORMAL_LAYER
|
||||
@@ -877,6 +877,7 @@ Note that amputating the affected organ does in fact remove the infection from t
|
||||
|
||||
switch(disintegrate)
|
||||
if(DROPLIMB_EDGE)
|
||||
appearance_flags &= ~PIXEL_SCALE
|
||||
compile_icon()
|
||||
add_blood(victim)
|
||||
var/matrix/M = matrix()
|
||||
|
||||
@@ -151,7 +151,7 @@ proc/cardinalrange(var/center)
|
||||
|
||||
/obj/machinery/am_shielding/proc/setup_core()
|
||||
processing = 1
|
||||
machines.Add(src)
|
||||
START_MACHINE_PROCESSING(src)
|
||||
if(!control_unit) return
|
||||
control_unit.linked_cores.Add(src)
|
||||
control_unit.reported_core_efficiency += efficiency
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
user << "<font color='blue'>You secure the generator to the floor.</font>"
|
||||
else
|
||||
user << "<font color='blue'>You unsecure the generator from the floor.</font>"
|
||||
makepowernets()
|
||||
SSmachines.makepowernets()
|
||||
else if(istype(O, /obj/item/weapon/screwdriver))
|
||||
open = !open
|
||||
playsound(loc, O.usesound, 50, 1)
|
||||
|
||||
@@ -230,22 +230,6 @@
|
||||
. += C
|
||||
return .
|
||||
|
||||
/hook/startup/proc/buildPowernets()
|
||||
return makepowernets()
|
||||
|
||||
// rebuild all power networks from scratch - only called at world creation or by the admin verb
|
||||
/proc/makepowernets()
|
||||
for(var/datum/powernet/PN in powernets)
|
||||
qdel(PN)
|
||||
powernets.Cut()
|
||||
|
||||
for(var/obj/structure/cable/PC in cable_list)
|
||||
if(!PC.powernet)
|
||||
var/datum/powernet/NewPN = new()
|
||||
NewPN.add_cable(PC)
|
||||
propagate_network(PC,PC.powernet)
|
||||
return 1
|
||||
|
||||
//remove the old powernet and replace it with a new one throughout the network.
|
||||
/proc/propagate_network(var/obj/O, var/datum/powernet/PN)
|
||||
//world.log << "propagating new network"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
var/problem = 0 // If this is not 0 there is some sort of issue in the powernet. Monitors will display warnings.
|
||||
|
||||
/datum/powernet/New()
|
||||
powernets += src
|
||||
START_PROCESSING_POWERNET(src)
|
||||
..()
|
||||
|
||||
/datum/powernet/Destroy()
|
||||
@@ -25,7 +25,7 @@
|
||||
for(var/obj/machinery/power/M in nodes)
|
||||
nodes -= M
|
||||
M.powernet = null
|
||||
powernets -= src
|
||||
STOP_PROCESSING_POWERNET(src)
|
||||
return ..()
|
||||
|
||||
//Returns the amount of excess power (before refunding to SMESs) from last tick.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
var/initial_capacitor_type = /obj/item/weapon/stock_parts/capacitor/adv
|
||||
var/slowdown_held = 2
|
||||
var/slowdown_worn = 1
|
||||
var/empty_sound = 'sound/machines/twobeep.ogg'
|
||||
|
||||
/obj/item/weapon/gun/magnetic/railgun/New()
|
||||
capacitor = new initial_capacitor_type(src)
|
||||
@@ -43,14 +44,13 @@
|
||||
var/obj/item/weapon/rcd_ammo/ammo = loaded
|
||||
ammo.remaining--
|
||||
if(ammo.remaining <= 0)
|
||||
spawn(3)
|
||||
playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
out_of_ammo()
|
||||
|
||||
/obj/item/weapon/gun/magnetic/railgun/proc/out_of_ammo()
|
||||
qdel(loaded)
|
||||
loaded.forceMove(get_turf(src))
|
||||
loaded = null
|
||||
visible_message("<span class='warning'>\The [src] beeps and ejects its empty cartridge.</span>")
|
||||
visible_message("<span class='warning'>\The [src] beeps and ejects its empty cartridge.</span>","<span class='warning'>There's a beeping sound!</span>")
|
||||
playsound(get_turf(src), empty_sound, 40, 1)
|
||||
|
||||
/obj/item/weapon/gun/magnetic/railgun/automatic // Adminspawn only, this shit is absurd.
|
||||
name = "\improper RHR accelerator"
|
||||
@@ -92,13 +92,9 @@
|
||||
projectile_type = /obj/item/projectile/bullet/magnetic/flechette
|
||||
loaded = /obj/item/weapon/magnetic_ammo
|
||||
fire_sound = 'sound/weapons/rapidslice.ogg'
|
||||
empty_sound = 'sound/weapons/smg_empty_alarm.ogg'
|
||||
|
||||
firemodes = list(
|
||||
list(mode_name="semiauto", burst=1, fire_delay=0, move_delay=null, one_handed_penalty=1, burst_accuracy=null, dispersion=null),
|
||||
list(mode_name="short bursts", burst=3, fire_delay=null, move_delay=5, one_handed_penalty=2, burst_accuracy=list(0,-1,-1), dispersion=list(0.0, 0.6, 1.0)),
|
||||
)
|
||||
|
||||
/obj/item/weapon/gun/magnetic/railgun/flechette/out_of_ammo()
|
||||
audible_message("<span class='warning'>\The [src] beeps to indicate the magazine is empty.</span>")
|
||||
playsound(loc, 'sound/weapons/smg_empty_alarm.ogg', 40, 1)
|
||||
..()
|
||||
|
||||
@@ -2172,7 +2172,7 @@
|
||||
result = "mojito"
|
||||
required_reagents = list("rum" = 3, "limejuice" = 1, "mint" = 1)
|
||||
result_amount = 5
|
||||
|
||||
|
||||
/datum/chemical_reaction/drinks/piscosour
|
||||
name = "Pisco Sour"
|
||||
id = "piscosour"
|
||||
@@ -2186,8 +2186,8 @@
|
||||
result = "coldfront"
|
||||
required_reagents = list("icecoffee" = 1, "whiskey" = 1, "mint" = 1)
|
||||
result_amount = 3
|
||||
|
||||
|
||||
|
||||
|
||||
//R-UST Port
|
||||
/datum/chemical_reaction/hyrdophoron
|
||||
name = "Hydrophoron"
|
||||
|
||||
@@ -14,19 +14,25 @@
|
||||
new /obj/item/weapon/reagent_containers/blood/empty(src)
|
||||
|
||||
/obj/item/weapon/reagent_containers/blood
|
||||
name = "BloodPack"
|
||||
desc = "Contains blood used for transfusion."
|
||||
name = "IV pack"
|
||||
var/base_name = " "
|
||||
desc = "Holds liquids used for transfusion."
|
||||
var/base_desc = " "
|
||||
icon = 'icons/obj/bloodpack.dmi'
|
||||
icon_state = "empty"
|
||||
item_state = "bloodpack_empty"
|
||||
volume = 200
|
||||
var/label_text = ""
|
||||
|
||||
var/blood_type = null
|
||||
|
||||
/obj/item/weapon/reagent_containers/blood/New()
|
||||
..()
|
||||
base_name = name
|
||||
base_desc = desc
|
||||
if(blood_type != null)
|
||||
name = "BloodPack [blood_type]"
|
||||
label_text = "[blood_type]"
|
||||
update_iv_label()
|
||||
reagents.add_reagent("blood", 200, list("donor"=null,"viruses"=null,"blood_DNA"=null,"blood_type"=blood_type,"resistances"=null,"trace_chem"=null))
|
||||
update_icon()
|
||||
|
||||
@@ -45,6 +51,30 @@
|
||||
icon_state = "full"
|
||||
item_state = "bloodpack_full"
|
||||
|
||||
/obj/item/weapon/reagent_containers/blood/attackby(obj/item/weapon/W as obj, mob/user as mob)
|
||||
if(istype(W, /obj/item/weapon/pen) || istype(W, /obj/item/device/flashlight/pen))
|
||||
var/tmp_label = sanitizeSafe(input(user, "Enter a label for [name]", "Label", label_text), MAX_NAME_LEN)
|
||||
if(length(tmp_label) > 50)
|
||||
to_chat(user, "<span class='notice'>The label can be at most 50 characters long.</span>")
|
||||
else if(length(tmp_label) > 10)
|
||||
to_chat(user, "<span class='notice'>You set the label.</span>")
|
||||
label_text = tmp_label
|
||||
update_iv_label()
|
||||
else
|
||||
to_chat(user, "<span class='notice'>You set the label to \"[tmp_label]\".</span>")
|
||||
label_text = tmp_label
|
||||
update_iv_label()
|
||||
|
||||
/obj/item/weapon/reagent_containers/blood/proc/update_iv_label()
|
||||
if(label_text == "")
|
||||
name = base_name
|
||||
else if(length(label_text) > 10)
|
||||
var/short_label_text = copytext(label_text, 1, 11)
|
||||
name = "[base_name] ([short_label_text]...)"
|
||||
else
|
||||
name = "[base_name] ([label_text])"
|
||||
desc = "[base_desc] It is labeled \"[label_text]\"."
|
||||
|
||||
/obj/item/weapon/reagent_containers/blood/APlus
|
||||
blood_type = "A+"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
/// Droppers.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/obj/item/weapon/reagent_containers/dropper
|
||||
name = "Dropper"
|
||||
desc = "A dropper. Transfers 5 units."
|
||||
name = "dropper"
|
||||
desc = "A dropper. Transfers up to 5 units at a time."
|
||||
icon = 'icons/obj/chemical.dmi'
|
||||
icon_state = "dropper0"
|
||||
amount_per_transfer_from_this = 5
|
||||
@@ -12,17 +12,25 @@
|
||||
slot_flags = SLOT_EARS
|
||||
volume = 5
|
||||
|
||||
/obj/item/weapon/reagent_containers/dropper/examine(var/mob/user)
|
||||
if(!..(user, 2))
|
||||
return
|
||||
if(reagents && reagents.reagent_list.len)
|
||||
to_chat(user, "<span class='notice'>It contains [reagents.total_volume] units of liquid.</span>")
|
||||
else
|
||||
to_chat(user, "<span class='notice'>It is empty.</span>")
|
||||
|
||||
/obj/item/weapon/reagent_containers/dropper/afterattack(var/obj/target, var/mob/user, var/proximity)
|
||||
if(!target.reagents || !proximity) return
|
||||
|
||||
if(reagents.total_volume)
|
||||
|
||||
if(!target.reagents.get_free_space())
|
||||
user << "<span class='notice'>[target] is full.</span>"
|
||||
to_chat(user, "<span class='notice'>[target] is full.</span>")
|
||||
return
|
||||
|
||||
if(!target.is_open_container() && !ismob(target) && !istype(target, /obj/item/weapon/reagent_containers/food) && !istype(target, /obj/item/clothing/mask/smokable/cigarette)) //You can inject humans and food but you cant remove the shit.
|
||||
user << "<span class='notice'>You cannot directly fill this object.</span>"
|
||||
to_chat(user, "<span class='notice'>You cannot directly fill this object.</span>")
|
||||
return
|
||||
|
||||
var/trans = 0
|
||||
@@ -50,7 +58,7 @@
|
||||
safe_thing = victim.glasses
|
||||
|
||||
if(safe_thing)
|
||||
trans = reagents.splash(safe_thing, amount_per_transfer_from_this, max_spill=30)
|
||||
trans = reagents.splash(safe_thing, min(amount_per_transfer_from_this, reagents.total_volume), max_spill=30)
|
||||
user.visible_message("<span class='warning'>[user] tries to squirt something into [target]'s eyes, but fails!</span>", "<span class='notice'>You transfer [trans] units of the solution.</span>")
|
||||
return
|
||||
|
||||
@@ -60,30 +68,30 @@
|
||||
user.attack_log += text("\[[time_stamp()]\] <font color='red'>Used the [name] to squirt [M.name] ([M.key]). Reagents: [contained]</font>")
|
||||
msg_admin_attack("[user.name] ([user.ckey]) squirted [M.name] ([M.key]) with [name]. Reagents: [contained] (INTENT: [uppertext(user.a_intent)]) (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)")
|
||||
|
||||
trans += reagents.splash(target, reagents.total_volume/2, max_spill=30)
|
||||
trans += reagents.trans_to_mob(target, reagents.total_volume/2, CHEM_BLOOD) //I guess it gets into the bloodstream through the eyes or something
|
||||
user.visible_message("<span class='warning'>[user] squirts something into [target]'s eyes!</span>", "<span class='notice'>You transfer [trans] units of the solution.</span>")
|
||||
|
||||
trans += reagents.trans_to_mob(target, min(amount_per_transfer_from_this, reagents.total_volume)/2, CHEM_INGEST) //Half injected, half ingested
|
||||
trans += reagents.trans_to_mob(target, min(amount_per_transfer_from_this, reagents.total_volume), CHEM_BLOOD) //I guess it gets into the bloodstream through the eyes or something
|
||||
user.visible_message("<span class='warning'>[user] squirts something into [target]'s eyes!</span>", "<span class='notice'>You transfer [trans] units of the solution.</span>")
|
||||
|
||||
return
|
||||
|
||||
else
|
||||
trans = reagents.splash(target, amount_per_transfer_from_this, max_spill=0) //sprinkling reagents on generic non-mobs
|
||||
user << "<span class='notice'>You transfer [trans] units of the solution.</span>"
|
||||
trans = reagents.trans_to_obj(target, amount_per_transfer_from_this)
|
||||
to_chat(user, "<span class='notice'>You transfer [trans] units of the solution.</span>")
|
||||
|
||||
else // Taking from something
|
||||
|
||||
if(!target.is_open_container() && !istype(target,/obj/structure/reagent_dispensers))
|
||||
user << "<span class='notice'>You cannot directly remove reagents from [target].</span>"
|
||||
to_chat(user, "<span class='notice'>You cannot directly remove reagents from [target].</span>")
|
||||
return
|
||||
|
||||
if(!target.reagents || !target.reagents.total_volume)
|
||||
user << "<span class='notice'>[target] is empty.</span>"
|
||||
to_chat(user, "<span class='notice'>[target] is empty.</span>")
|
||||
return
|
||||
|
||||
var/trans = target.reagents.trans_to_obj(src, amount_per_transfer_from_this)
|
||||
|
||||
user << "<span class='notice'>You fill the dropper with [trans] units of the solution.</span>"
|
||||
to_chat(user, "<span class='notice'>You fill the dropper with [trans] units of the solution.</span>")
|
||||
|
||||
return
|
||||
|
||||
@@ -98,7 +106,7 @@
|
||||
|
||||
/obj/item/weapon/reagent_containers/dropper/industrial
|
||||
name = "Industrial Dropper"
|
||||
desc = "A larger dropper. Transfers 10 units."
|
||||
desc = "A larger dropper. Transfers up to 10 units at a time."
|
||||
amount_per_transfer_from_this = 10
|
||||
possible_transfer_amounts = list(1,2,3,4,5,6,7,8,9,10)
|
||||
volume = 10
|
||||
|
||||
@@ -376,7 +376,7 @@
|
||||
return
|
||||
|
||||
Bumped(var/atom/movable/AM) //Go straight into the chute
|
||||
if(istype(AM, /obj/item/projectile) || istype(AM, /obj/effect)) return
|
||||
if(istype(AM, /obj/item/projectile) || istype(AM, /obj/effect) || istype(AM, /obj/mecha)) return
|
||||
switch(dir)
|
||||
if(NORTH)
|
||||
if(AM.loc.y != src.loc.y+1) return
|
||||
|
||||
@@ -290,6 +290,14 @@
|
||||
id = "mech_taser"
|
||||
build_path = /obj/item/mecha_parts/mecha_equipment/weapon/energy/taser
|
||||
|
||||
/datum/design/item/mecha/shocker
|
||||
name = "Exosuit Electrifier"
|
||||
desc = "A device to electrify the external portions of a mecha in order to increase its defensive capabilities."
|
||||
id = "mech_shocker"
|
||||
req_tech = list(TECH_COMBAT = 3, TECH_POWER = 6, TECH_MAGNET = 1)
|
||||
build_path = /obj/item/mecha_parts/mecha_equipment/shocker
|
||||
materials = list(DEFAULT_WALL_MATERIAL = 3500, "gold" = 750, "glass" = 1000)
|
||||
|
||||
/datum/design/item/mecha/lmg
|
||||
name = "Ultra AC 2"
|
||||
id = "mech_lmg"
|
||||
@@ -407,7 +415,13 @@
|
||||
materials = list(DEFAULT_WALL_MATERIAL = 7500, "gold" = 750, "silver" = 1500, "glass" = 3750)
|
||||
build_path = /obj/item/mecha_parts/mecha_equipment/repair_droid
|
||||
|
||||
//obj/item/mecha_parts/mecha_equipment/jetpack, //TODO MECHA JETPACK SPRITE MISSING
|
||||
/datum/design/item/mecha/jetpack
|
||||
name = "Ion Jetpack"
|
||||
desc = "Using directed ion bursts and cunning solar wind reflection technique, this device enables controlled space flight."
|
||||
id = "mech_jetpack"
|
||||
req_tech = list(TECH_ENGINEERING = 3, TECH_MAGNET = 4) //One less magnet than the actual got-damn teleporter.
|
||||
build_path = /obj/item/mecha_parts/mecha_equipment/tool/jetpack
|
||||
materials = list(DEFAULT_WALL_MATERIAL = 7500, "silver" = 300, "glass" = 600)
|
||||
|
||||
/datum/design/item/mecha/phoron_generator
|
||||
desc = "Phoron Reactor"
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
break
|
||||
|
||||
if(update_power)
|
||||
makepowernets()
|
||||
SSmachines.makepowernets()
|
||||
return
|
||||
|
||||
//returns 1 if the shuttle has a valid arrive time
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
origin.move_contents_to(destination)
|
||||
|
||||
if((locate(/obj/machinery/power) in destination) || (locate(/obj/structure/cable) in destination))
|
||||
makepowernets()
|
||||
SSmachines.makepowernets()
|
||||
|
||||
current_floor = next_floor
|
||||
control_panel_interior.visible_message("The elevator [moving_upwards ? "rises" : "descends"] smoothly.")
|
||||
|
||||
@@ -247,5 +247,5 @@
|
||||
|
||||
/obj/effect/suspension_field/Destroy()
|
||||
for(var/atom/movable/I in src)
|
||||
I.loc = src.loc
|
||||
..()
|
||||
I.dropInto(loc)
|
||||
return ..()
|
||||
|
||||
@@ -956,15 +956,21 @@
|
||||
result_amount = 1
|
||||
required = /obj/item/slime_extract/rainbow
|
||||
|
||||
/datum/chemical_reaction/slime/rainbow_random_slime/on_reaction(var/datum/reagents/holder)
|
||||
var/list/forbidden_types = list(
|
||||
/mob/living/simple_animal/slime/rainbow/kendrick
|
||||
)
|
||||
var/list/potential_types = typesof(/mob/living/simple_animal/slime) - forbidden_types
|
||||
var/slime_type = pick(potential_types)
|
||||
new slime_type(get_turf(holder.my_atom))
|
||||
..()
|
||||
|
||||
/datum/chemical_reaction/slime/rainbow_random_slime/on_reaction(var/datum/reagents/holder)
|
||||
var/mob/living/simple_animal/slime/S
|
||||
var/list/slime_types = typesof(/mob/living/simple_animal/slime)
|
||||
|
||||
while(slime_types.len)
|
||||
S = pick(slime_types)
|
||||
if(initial(S.rainbow_core_candidate) == TRUE)
|
||||
break
|
||||
else
|
||||
slime_types -= S
|
||||
S = null
|
||||
|
||||
if(S)
|
||||
new S(get_turf(holder.my_atom))
|
||||
|
||||
/datum/chemical_reaction/slime/rainbow_unity
|
||||
name = "Slime Unity"
|
||||
|
||||
@@ -42,9 +42,9 @@
|
||||
)
|
||||
|
||||
// Some maps have areas specific to the map, so include those.
|
||||
exempt_areas += using_map.unit_test_exempt_areas
|
||||
exempt_from_atmos += using_map.unit_test_exempt_from_atmos
|
||||
exempt_from_apc += using_map.unit_test_exempt_from_apc
|
||||
exempt_areas += using_map.unit_test_exempt_areas.Copy()
|
||||
exempt_from_atmos += using_map.unit_test_exempt_from_atmos.Copy()
|
||||
exempt_from_apc += using_map.unit_test_exempt_from_apc.Copy()
|
||||
|
||||
for(var/area/A in world)
|
||||
if(A.z == 1 && !(A.type in exempt_areas))
|
||||
|
||||
@@ -34,7 +34,7 @@ var/global/datum/global_init/init = new ()
|
||||
turf = /turf/space
|
||||
area = /area/space
|
||||
view = "15x15"
|
||||
cache_lifespan = 0 //stops player uploaded stuff from being kept in the rsc past the current session
|
||||
cache_lifespan = 7
|
||||
|
||||
|
||||
|
||||
|
||||
5
html/changelogs/Atermonera - Weatherapp.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
author: Atermonera
|
||||
|
||||
delete-after: True
|
||||
|
||||
- rscadd: "Communicators now have a weather app."
|
||||
4
html/changelogs/Leshana-ss-machines.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
author: Leshana
|
||||
delete-after: True
|
||||
changes:
|
||||
- tweak: "Convert the machinery controller to a StonedMC subsystem"
|
||||
4
html/changelogs/Leshana-tgport.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
author: MrStonedOne
|
||||
delete-after: True
|
||||
changes:
|
||||
- rscadd: "Added admin verb 'Display del() Log' displaying garabage collector statistics."
|
||||
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 327 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |