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
This commit is contained in:
Leshana
2018-01-04 22:32:13 -05:00
139 changed files with 4921 additions and 4047 deletions

View File

@@ -180,7 +180,6 @@
/obj/machinery/atmospherics/tvalve/process()
..()
. = PROCESS_KILL
//machines.Remove(src)
return

View File

@@ -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)

View File

@@ -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()

View File

@@ -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))

View File

@@ -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;
*/

View File

@@ -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

View File

@@ -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)

View File

@@ -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) )

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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() )

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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
View 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

View File

@@ -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))

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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.

View File

@@ -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>"

View File

@@ -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>&nbsp;[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

View File

@@ -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>&nbsp;[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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View 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)

View File

@@ -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)

View 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)

View 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")

View 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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -100,3 +100,4 @@
icon_state = "survivalknife"
item_state = "knife"
applies_material_colour = FALSE
should_cleave = FALSE

View File

@@ -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"

View File

@@ -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)

View File

@@ -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.

View File

@@ -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

View File

@@ -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( )

View File

@@ -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)

View File

@@ -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'

View File

@@ -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

View File

@@ -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,

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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>&nbsp</a></td><td><a>&nbsp</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'>//>&nbsp</a></td><td><a>&nbsp</a></td></tr>"
*******/
. += "</table></td><td width='20%'><table width='100%' cellpadding='1' cellspacing='0'>"
index = 0

View File

@@ -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

View File

@@ -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()

View File

@@ -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))

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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()

View File

@@ -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

View 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

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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.

View File

@@ -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)
..()

View File

@@ -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"

View File

@@ -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+"

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -223,7 +223,7 @@
break
if(update_power)
makepowernets()
SSmachines.makepowernets()
return
//returns 1 if the shuttle has a valid arrive time

View File

@@ -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.")

View File

@@ -247,5 +247,5 @@
/obj/effect/suspension_field/Destroy()
for(var/atom/movable/I in src)
I.loc = src.loc
..()
I.dropInto(loc)
return ..()

View File

@@ -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"

View File

@@ -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))

View File

@@ -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

View File

@@ -0,0 +1,5 @@
author: Atermonera
delete-after: True
- rscadd: "Communicators now have a weather app."

View File

@@ -0,0 +1,4 @@
author: Leshana
delete-after: True
changes:
- tweak: "Convert the machinery controller to a StonedMC subsystem"

View File

@@ -0,0 +1,4 @@
author: MrStonedOne
delete-after: True
changes:
- rscadd: "Added admin verb 'Display del() Log' displaying garabage collector statistics."

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Some files were not shown because too many files have changed in this diff Show More