mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-01-03 05:51:56 +00:00
Merge branch 'dev' of https://github.com/Baystation12/Baystation12 into fluff2
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
#include "code\__defines\_compile_options.dm"
|
||||
#include "code\__defines\admin.dm"
|
||||
#include "code\__defines\atmos.dm"
|
||||
#include "code\__defines\btime.dm"
|
||||
#include "code\__defines\chemistry.dm"
|
||||
#include "code\__defines\damage_organs.dm"
|
||||
#include "code\__defines\dna.dm"
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "code\__defines\math_physics.dm"
|
||||
#include "code\__defines\misc.dm"
|
||||
#include "code\__defines\mobs.dm"
|
||||
#include "code\__defines\process_scheduler.dm"
|
||||
#include "code\__defines\research.dm"
|
||||
#include "code\__defines\species_languages.dm"
|
||||
#include "code\__defines\turfs.dm"
|
||||
@@ -47,6 +49,7 @@
|
||||
#include "code\_helpers\mobs.dm"
|
||||
#include "code\_helpers\names.dm"
|
||||
#include "code\_helpers\sanitize_values.dm"
|
||||
#include "code\_helpers\spawn_sync.dm"
|
||||
#include "code\_helpers\text.dm"
|
||||
#include "code\_helpers\time.dm"
|
||||
#include "code\_helpers\turfs.dm"
|
||||
@@ -119,6 +122,8 @@
|
||||
#include "code\controllers\subsystems.dm"
|
||||
#include "code\controllers\verbs.dm"
|
||||
#include "code\controllers\voting.dm"
|
||||
#include "code\controllers\observer_listener\atom\observer.dm"
|
||||
#include "code\controllers\observer_listener\datum\observer.dm"
|
||||
#include "code\controllers\Processes\air.dm"
|
||||
#include "code\controllers\Processes\alarm.dm"
|
||||
#include "code\controllers\Processes\chemistry.dm"
|
||||
@@ -137,13 +142,10 @@
|
||||
#include "code\controllers\Processes\ticker.dm"
|
||||
#include "code\controllers\Processes\turf.dm"
|
||||
#include "code\controllers\Processes\vote.dm"
|
||||
#include "code\controllers\ProcessScheduler\core\_define.dm"
|
||||
#include "code\controllers\Processes\wireless.dm"
|
||||
#include "code\controllers\ProcessScheduler\core\_stubs.dm"
|
||||
#include "code\controllers\ProcessScheduler\core\process.dm"
|
||||
#include "code\controllers\ProcessScheduler\core\processScheduler.dm"
|
||||
#include "code\controllers\ProcessScheduler\core\updateQueue.dm"
|
||||
#include "code\controllers\ProcessScheduler\core\updateQueueWorker.dm"
|
||||
#include "code\controllers\subsystem\alarms.dm"
|
||||
#include "code\datums\ai_law_sets.dm"
|
||||
#include "code\datums\ai_laws.dm"
|
||||
#include "code\datums\browser.dm"
|
||||
@@ -208,6 +210,7 @@
|
||||
#include "code\datums\wires\camera.dm"
|
||||
#include "code\datums\wires\explosive.dm"
|
||||
#include "code\datums\wires\mulebot.dm"
|
||||
#include "code\datums\wires\nuclearbomb.dm"
|
||||
#include "code\datums\wires\particle_accelerator.dm"
|
||||
#include "code\datums\wires\radio.dm"
|
||||
#include "code\datums\wires\robot.dm"
|
||||
@@ -599,6 +602,7 @@
|
||||
#include "code\game\objects\items\devices\flash.dm"
|
||||
#include "code\game\objects\items\devices\flashlight.dm"
|
||||
#include "code\game\objects\items\devices\floor_painter.dm"
|
||||
#include "code\game\objects\items\devices\hacktool.dm"
|
||||
#include "code\game\objects\items\devices\lightreplacer.dm"
|
||||
#include "code\game\objects\items\devices\megaphone.dm"
|
||||
#include "code\game\objects\items\devices\modkit.dm"
|
||||
@@ -1066,11 +1070,19 @@
|
||||
#include "code\modules\clothing\under\jobs\security.dm"
|
||||
#include "code\modules\clothing\under\xenos\resomi.dm"
|
||||
#include "code\modules\customitems\item_spawning.dm"
|
||||
#include "code\modules\detectivework\evidence.dm"
|
||||
#include "code\modules\detectivework\footprints_and_rag.dm"
|
||||
#include "code\modules\detectivework\footprints.dm"
|
||||
#include "code\modules\detectivework\forensics.dm"
|
||||
#include "code\modules\detectivework\scanner.dm"
|
||||
#include "code\modules\detectivework\scanning_console.dm"
|
||||
#include "code\modules\detectivework\microscope\dnascanner.dm"
|
||||
#include "code\modules\detectivework\microscope\microscope.dm"
|
||||
#include "code\modules\detectivework\microscope\slides.dm"
|
||||
#include "code\modules\detectivework\tools\crimekit.dm"
|
||||
#include "code\modules\detectivework\tools\evidencebag.dm"
|
||||
#include "code\modules\detectivework\tools\luminol.dm"
|
||||
#include "code\modules\detectivework\tools\rag.dm"
|
||||
#include "code\modules\detectivework\tools\sample_kits.dm"
|
||||
#include "code\modules\detectivework\tools\storage.dm"
|
||||
#include "code\modules\detectivework\tools\swabs.dm"
|
||||
#include "code\modules\detectivework\tools\uvlight.dm"
|
||||
#include "code\modules\economy\Accounts.dm"
|
||||
#include "code\modules\economy\Accounts_DB.dm"
|
||||
#include "code\modules\economy\ATM.dm"
|
||||
@@ -1829,6 +1841,8 @@
|
||||
#include "code\modules\virus2\helpers.dm"
|
||||
#include "code\modules\virus2\isolator.dm"
|
||||
#include "code\modules\virus2\items_devices.dm"
|
||||
#include "code\modules\wireless\devices.dm"
|
||||
#include "code\modules\wireless\interfaces.dm"
|
||||
#include "code\modules\xgm\xgm_gas_data.dm"
|
||||
#include "code\modules\xgm\xgm_gas_mixture.dm"
|
||||
#include "code\ZAS\_docs.dm"
|
||||
|
||||
@@ -35,10 +35,9 @@ except ImportError:
|
||||
try:
|
||||
tiedosto = open("psycodownload.txt","r")
|
||||
except:
|
||||
tiedosto = open("psycodownload.txt","w")
|
||||
tiedosto.write("http://www.voidspace.org.uk/python/modules.shtml#psyco")
|
||||
tiedosto.write("\nhttp://psyco.sourceforge.net/download.html")
|
||||
tiedosto.close()
|
||||
with open("psycodownload.txt","w") as tiedosto:
|
||||
tiedosto.write("http://www.voidspace.org.uk/python/modules.shtml#psyco")
|
||||
tiedosto.write("\nhttp://psyco.sourceforge.net/download.html")
|
||||
print "Check psycodownload.txt for a link"
|
||||
else:
|
||||
print "For god's sake, open psycodownload.txt"
|
||||
@@ -190,15 +189,13 @@ tell_list = {}
|
||||
if CORE_DATA.DISABLE_ALL_NON_MANDATORY_SOCKET_CONNECTIONS:
|
||||
nudgeable = False
|
||||
try:
|
||||
tiedosto = open("replacenames.cache","r")
|
||||
replacenames = pickle.load(tiedosto)
|
||||
tiedosto.close()
|
||||
with open("replacenames.cache","r") as tiedosto:
|
||||
replacenames = pickle.load(tiedosto)
|
||||
for i in replacenames.values():
|
||||
if len(i) > call_me_max_length:
|
||||
replacenames[replacenames.keys()[replacenames.values().index(i)]] = i[:call_me_max_length]
|
||||
tiedosto = open("replacenames.cache","w")
|
||||
pickle.dump(replacenames,tiedosto)
|
||||
tiedosto.close()
|
||||
with open("replacenames.cache","w") as tiedosto:
|
||||
pickle.dump(replacenames,tiedosto)
|
||||
if "[\0x01]" in i.lower() or "[\\0x01]" in i.lower():
|
||||
i = i.replace("[\0x01]","")
|
||||
i = i.replace("[\0X01]","")
|
||||
@@ -211,13 +208,12 @@ except EOFError: #Cache corrupt
|
||||
replacenames = {}
|
||||
print "replacenames.cache is corrupt and couldn't be loaded."
|
||||
try:
|
||||
tiedosto = open("peopleheknows.cache","r")
|
||||
peopleheknows = pickle.load(tiedosto)
|
||||
tiedosto.close()
|
||||
with open("peopleheknows.cache","r") as tiedosto:
|
||||
peopleheknows = pickle.load(tiedosto)
|
||||
except IOError:
|
||||
peopleheknows = [[],[]]
|
||||
tiedosto = open("peopleheknows.cache","w")
|
||||
tiedosto.close()
|
||||
with open("peopleheknows.cache","w") as tiedosto:
|
||||
pass
|
||||
except EOFError:
|
||||
peopleheknows = [[],[]]
|
||||
print "peopleheknows.cache is corrupt and couldn't be loaded."
|
||||
@@ -345,16 +341,14 @@ if aggressive_pinging:
|
||||
self_time = 0
|
||||
global backup,disconnects,conn
|
||||
while disconnects < 5:
|
||||
if backup > self_time:
|
||||
if time.time()-backup > delay:
|
||||
conn.send("PONG "+pongtarg)
|
||||
print "Ponged"
|
||||
self_time = time.time()
|
||||
else:
|
||||
if time.time()-self_time > delay:
|
||||
conn.send("PONG "+pongtarg)
|
||||
print "Ponged"
|
||||
self_time = time.time()
|
||||
if backup > self_time and time.time()-backup > delay:
|
||||
conn.send("PONG "+pongtarg)
|
||||
print "Ponged"
|
||||
self_time = time.time()
|
||||
elif time.time()-self_time > delay:
|
||||
conn.send("PONG "+pongtarg)
|
||||
print "Ponged"
|
||||
self_time = time.time()
|
||||
time.sleep(refresh)
|
||||
thread.start_new_thread(aggressive_ping,(aggressive_pinging_delay,aggressive_pinging_refresh,))
|
||||
def stop(sender,debug=1):
|
||||
@@ -370,16 +364,15 @@ def stop(sender,debug=1):
|
||||
access_granted = True
|
||||
else:
|
||||
access_granted = False
|
||||
if access_granted:
|
||||
if debug:
|
||||
print sender+":"+prefix+"stop"
|
||||
if random.randint(0,100) == 50:
|
||||
conn.privmsg(channel,"Hammertime!")
|
||||
else:
|
||||
conn.privmsg(channel,"Shutting down.")
|
||||
disconnects = 99999
|
||||
conn.quit()
|
||||
return True
|
||||
if access_granted and debug:
|
||||
print sender+":"+prefix+"stop"
|
||||
if random.randint(0,100) == 50:
|
||||
conn.privmsg(channel,"Hammertime!")
|
||||
else:
|
||||
conn.privmsg(channel,"Shutting down.")
|
||||
disconnects = 99999
|
||||
conn.quit()
|
||||
return True
|
||||
else:
|
||||
conn.privmsg(channel,"You cannot command me")
|
||||
return False
|
||||
@@ -401,13 +394,12 @@ def target(who,how_long):
|
||||
if debug:
|
||||
print "Banned",who,"For",how_long,"seconds"
|
||||
if logbans:
|
||||
tiedosto = open(targetdirectory+"banlog/"+str(int(start))+"-"+str(int(end))+".txt","w")
|
||||
tiedosto.write("Start of ban on "+who+":"+str(int(start)))
|
||||
tiedosto.write("\n")
|
||||
tiedosto.write("End of ban on "+who+":"+str(int(end)))
|
||||
tiedosto.write("\n")
|
||||
tiedosto.write("In total:"+str(int(end-start))+"Seconds")
|
||||
tiedosto.close()
|
||||
with open(targetdirectory+"banlog/"+str(int(start))+"-"+str(int(end))+".txt","w") as tiedosto:
|
||||
tiedosto.write("Start of ban on "+who+":"+str(int(start)))
|
||||
tiedosto.write("\n")
|
||||
tiedosto.write("End of ban on "+who+":"+str(int(end)))
|
||||
tiedosto.write("\n")
|
||||
tiedosto.write("In total:"+str(int(end-start))+"Seconds")
|
||||
else:
|
||||
CALL_OFF = False
|
||||
pass
|
||||
@@ -576,13 +568,12 @@ while True:
|
||||
time.sleep(6)
|
||||
Name = origname
|
||||
conn.nick(Name)
|
||||
if origname in truesender:
|
||||
if influx == prefix+"stop":
|
||||
time.sleep(0.5) #A small delay
|
||||
conn.privmsg(channel,"Shutting down.")
|
||||
conn.quit()
|
||||
disconnects = 99999
|
||||
break
|
||||
if origname in truesender and influx == prefix+"stop":
|
||||
time.sleep(0.5) #A small delay
|
||||
conn.privmsg(channel,"Shutting down.")
|
||||
conn.quit()
|
||||
disconnects = 99999
|
||||
break
|
||||
if len(translateable) > 0 and enabled == True:
|
||||
people = "-5|5|1-".join(users).lower()
|
||||
if truesender.lower() in translateable:
|
||||
@@ -633,9 +624,8 @@ while True:
|
||||
arg = influx.lower()[8+len(prefix):]
|
||||
if debug:
|
||||
print truesender+":"+prefix+"suggest "+arg
|
||||
tiedosto = open(targetdirectory+"suggestions/suggestions_"+str(int(time.time()))+".txt","a")
|
||||
tiedosto.write(arg)
|
||||
tiedosto.close()
|
||||
with open(targetdirectory+"suggestions/suggestions_"+str(int(time.time()))+".txt","a") as tiedosto:
|
||||
tiedosto.write(arg)
|
||||
conn.privmsg(targetchannel,"Suggestion received")
|
||||
elif cocheck( prefix+"help "): #Space in front of the ( to make sure that my command finder does not pick this up.
|
||||
arg = " ".join(influx.split(" ")[1:]).lower()
|
||||
|
||||
18
code/__defines/btime.dm
Normal file
18
code/__defines/btime.dm
Normal file
@@ -0,0 +1,18 @@
|
||||
// Comment this out if the external btime library is unavailable
|
||||
#define PRECISE_TIMER_AVAILABLE
|
||||
|
||||
#ifdef PRECISE_TIMER_AVAILABLE
|
||||
var/global/__btime__libName = "btime.[world.system_type==MS_WINDOWS?"dll":"so"]"
|
||||
#define TimeOfHour (__extern__timeofhour)
|
||||
#define __extern__timeofhour text2num(call(__btime__libName, "gettime")())
|
||||
/hook/startup/proc/checkbtime()
|
||||
try
|
||||
// This will always return 1 unless the btime library cannot be accessed
|
||||
if(TimeOfHour || 1) return 1
|
||||
catch(var/exception/e)
|
||||
log_to_dd("PRECISE_TIMER_AVAILABLE is defined in btime.dm, but calling the btime library failed: [e]")
|
||||
log_to_dd("This is a fatal error. The world will now shut down.")
|
||||
del(world)
|
||||
#else
|
||||
#define TimeOfHour (world.timeofday % 36000)
|
||||
#endif
|
||||
@@ -63,24 +63,24 @@
|
||||
#define LIFE_HUD 10 // STATUS_HUD that only reports dead or alive
|
||||
|
||||
//some colors
|
||||
#define COLOR_WHITE "#FFFFFF"
|
||||
#define COLOR_SILVER "#C0C0C0"
|
||||
#define COLOR_GRAY "#808080"
|
||||
#define COLOR_BLACK "#000000"
|
||||
#define COLOR_RED "#FF0000"
|
||||
#define COLOR_MAROON "#800000"
|
||||
#define COLOR_YELLOW "#FFFF00"
|
||||
#define COLOR_OLIVE "#808000"
|
||||
#define COLOR_LIME "#00FF00"
|
||||
#define COLOR_GREEN "#008000"
|
||||
#define COLOR_CYAN "#00FFFF"
|
||||
#define COLOR_TEAL "#008080"
|
||||
#define COLOR_BLUE "#0000FF"
|
||||
#define COLOR_NAVY "#000080"
|
||||
#define COLOR_PINK "#FF00FF"
|
||||
#define COLOR_PURPLE "#800080"
|
||||
#define COLOR_ORANGE "#FF9900"
|
||||
|
||||
#define COLOR_WHITE "#FFFFFF"
|
||||
#define COLOR_SILVER "#C0C0C0"
|
||||
#define COLOR_GRAY "#808080"
|
||||
#define COLOR_BLACK "#000000"
|
||||
#define COLOR_RED "#FF0000"
|
||||
#define COLOR_MAROON "#800000"
|
||||
#define COLOR_YELLOW "#FFFF00"
|
||||
#define COLOR_OLIVE "#808000"
|
||||
#define COLOR_LIME "#00FF00"
|
||||
#define COLOR_GREEN "#008000"
|
||||
#define COLOR_CYAN "#00FFFF"
|
||||
#define COLOR_TEAL "#008080"
|
||||
#define COLOR_BLUE "#0000FF"
|
||||
#define COLOR_NAVY "#000080"
|
||||
#define COLOR_PINK "#FF00FF"
|
||||
#define COLOR_PURPLE "#800080"
|
||||
#define COLOR_ORANGE "#FF9900"
|
||||
#define COLOR_LUMINOL "#66FFFF"
|
||||
// Shuttles.
|
||||
|
||||
// These define the time taken for the shuttle to get to the space station, and the time before it leaves again.
|
||||
@@ -181,3 +181,8 @@
|
||||
#define COLOR_PALE_RED_GRAY "#CC9090"
|
||||
#define COLOR_PALE_PURPLE_GRAY "#BDA2BA"
|
||||
#define COLOR_PURPLE_GRAY "#A2819E"
|
||||
|
||||
//Camera capture modes
|
||||
#define CAPTURE_MODE_REGULAR 0 //Regular polaroid camera mode
|
||||
#define CAPTURE_MODE_ALL 1 //Admin camera mode
|
||||
#define CAPTURE_MODE_PARTIAL 3 //Simular to regular mode, but does not do dummy check
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
// Process status defines
|
||||
#define PROCESS_STATUS_IDLE 1
|
||||
#define PROCESS_STATUS_QUEUED 2
|
||||
#define PROCESS_STATUS_RUNNING 3
|
||||
#define PROCESS_STATUS_MAYBE_HUNG 4
|
||||
#define PROCESS_STATUS_PROBABLY_HUNG 5
|
||||
#define PROCESS_STATUS_HUNG 6
|
||||
|
||||
// Process time thresholds
|
||||
#define PROCESS_DEFAULT_HANG_WARNING_TIME 300 // 30 seconds
|
||||
#define PROCESS_DEFAULT_HANG_ALERT_TIME 600 // 60 seconds
|
||||
#define PROCESS_DEFAULT_HANG_RESTART_TIME 900 // 90 seconds
|
||||
#define PROCESS_DEFAULT_SCHEDULE_INTERVAL 50 // 50 ticks
|
||||
#define PROCESS_DEFAULT_SLEEP_INTERVAL 2 // 2 ticks
|
||||
#define PROCESS_DEFAULT_CPU_THRESHOLD 90 // 90%
|
||||
|
||||
//#define UPDATE_QUEUE_DEBUG
|
||||
// Process status defines
|
||||
#define PROCESS_STATUS_IDLE 1
|
||||
#define PROCESS_STATUS_QUEUED 2
|
||||
#define PROCESS_STATUS_RUNNING 3
|
||||
#define PROCESS_STATUS_MAYBE_HUNG 4
|
||||
#define PROCESS_STATUS_PROBABLY_HUNG 5
|
||||
#define PROCESS_STATUS_HUNG 6
|
||||
|
||||
// Process time thresholds
|
||||
#define PROCESS_DEFAULT_HANG_WARNING_TIME 300 // 30 seconds
|
||||
#define PROCESS_DEFAULT_HANG_ALERT_TIME 600 // 60 seconds
|
||||
#define PROCESS_DEFAULT_HANG_RESTART_TIME 900 // 90 seconds
|
||||
#define PROCESS_DEFAULT_SCHEDULE_INTERVAL 50 // 50 ticks
|
||||
#define PROCESS_DEFAULT_SLEEP_INTERVAL 8 // 2 ticks
|
||||
#define PROCESS_DEFAULT_CPU_THRESHOLD 90 // 90%
|
||||
|
||||
// SCHECK macros
|
||||
// This references src directly to work around a weird bug with try/catch
|
||||
#define SCHECK_EVERY(this_many_calls) if(++src.calls_since_last_scheck >= this_many_calls) sleepCheck()
|
||||
#define SCHECK SCHECK_EVERY(50)
|
||||
@@ -74,7 +74,6 @@ var/global/list/GlobalPool = list()
|
||||
|
||||
D.Destroy()
|
||||
D.ResetVars()
|
||||
D.disposed = 1 //Set to stop processing while pooled
|
||||
|
||||
/proc/IsPooled(var/datum/D)
|
||||
if(isnull(GlobalPool[D.type]))
|
||||
@@ -86,7 +85,6 @@ var/global/list/GlobalPool = list()
|
||||
New(arglist(args))
|
||||
else
|
||||
New(args)
|
||||
disposed = null
|
||||
|
||||
/atom/movable/Prepare(args)
|
||||
var/list/args_list = args
|
||||
|
||||
@@ -360,6 +360,13 @@ proc/isInSight(var/atom/A, var/atom/B)
|
||||
for(var/client/C in group)
|
||||
C.screen -= O
|
||||
|
||||
/proc/flick_overlay(image/I, list/show_to, duration)
|
||||
for(var/client/C in show_to)
|
||||
C.images += I
|
||||
spawn(duration)
|
||||
for(var/client/C in show_to)
|
||||
C.images -= I
|
||||
|
||||
datum/projectile_data
|
||||
var/src_x
|
||||
var/src_y
|
||||
|
||||
@@ -760,7 +760,11 @@ proc // Creates a single icon from a given /atom or /image. Only the first argu
|
||||
// Pull the default direction.
|
||||
add = icon(I:icon, I:icon_state)
|
||||
else // 'I' is an appearance object.
|
||||
add = getFlatIcon(new/image(I), curdir, curicon, curstate, curblend)
|
||||
if(istype(A,/obj/machinery/atmospherics) && I in A.underlays)
|
||||
var/image/Im = I
|
||||
add = getFlatIcon(new/image(I), Im.dir, curicon, curstate, curblend, 1)
|
||||
else
|
||||
add = getFlatIcon(new/image(I), curdir, curicon, curstate, curblend, always_use_defdir)
|
||||
|
||||
// Find the new dimensions of the flat icon to fit the added overlay
|
||||
addX1 = min(flatX1, I:pixel_x+1)
|
||||
@@ -773,9 +777,15 @@ proc // Creates a single icon from a given /atom or /image. Only the first argu
|
||||
flat.Crop(addX1-flatX1+1, addY1-flatY1+1, addX2-flatX1+1, addY2-flatY1+1)
|
||||
flatX1=addX1;flatX2=addX2
|
||||
flatY1=addY1;flatY2=addY2
|
||||
|
||||
var/iconmode
|
||||
if(I in A.overlays)
|
||||
iconmode = ICON_OVERLAY
|
||||
else if(I in A.underlays)
|
||||
iconmode = ICON_UNDERLAY
|
||||
else
|
||||
iconmode = blendMode2iconMode(curblend)
|
||||
// Blend the overlay into the flattened icon
|
||||
flat.Blend(add, blendMode2iconMode(curblend), I:pixel_x + 2 - flatX1, I:pixel_y + 2 - flatY1)
|
||||
flat.Blend(add, iconmode, I:pixel_x + 2 - flatX1, I:pixel_y + 2 - flatY1)
|
||||
|
||||
if(A.color)
|
||||
flat.Blend(A.color, ICON_MULTIPLY)
|
||||
@@ -853,3 +863,53 @@ proc/sort_atoms_by_layer(var/list/atoms)
|
||||
result.Swap(i, gap + i)
|
||||
swapped = 1
|
||||
return result
|
||||
/*
|
||||
generate_image function generates image of specified range and location
|
||||
arguments tx, ty, tz are target coordinates (requred), range defines render distance to opposite corner (requred)
|
||||
cap_mode is capturing mode (optional), user is capturing mob (requred only wehen cap_mode = CAPTURE_MODE_REGULAR),
|
||||
lighting determines lighting capturing (optional), suppress_errors suppreses errors and continues to capture (optional).
|
||||
*/
|
||||
proc/generate_image(var/tx as num, var/ty as num, var/tz as num, var/range as num, var/cap_mode = CAPTURE_MODE_PARTIAL, var/mob/living/user, var/lighting = 1, var/suppress_errors = 1)
|
||||
var/list/turfstocapture = list()
|
||||
//Lines below determine what tiles will be rendered
|
||||
for(var/xoff = 0 to range)
|
||||
for(var/yoff = 0 to range)
|
||||
var/turf/T = locate(tx + xoff,ty + yoff,tz)
|
||||
if(T)
|
||||
if(cap_mode == CAPTURE_MODE_REGULAR)
|
||||
if(user.can_capture_turf(T))
|
||||
turfstocapture.Add(T)
|
||||
continue
|
||||
else
|
||||
turfstocapture.Add(T)
|
||||
else
|
||||
//Capture includes non-existan turfs
|
||||
if(!suppress_errors)
|
||||
return
|
||||
//Lines below determine what objects will be rendered
|
||||
var/list/atoms = list()
|
||||
for(var/turf/T in turfstocapture)
|
||||
atoms.Add(T)
|
||||
for(var/atom/A in T)
|
||||
if(istype(A, /atom/movable/lighting_overlay) && lighting) //Special case for lighting
|
||||
atoms.Add(A)
|
||||
continue
|
||||
if(A.invisibility) continue
|
||||
atoms.Add(A)
|
||||
//Lines below actually render all colected data
|
||||
atoms = sort_atoms_by_layer(atoms)
|
||||
var/icon/cap = icon('icons/effects/96x96.dmi', "")
|
||||
cap.Scale(range*32, range*32)
|
||||
cap.Blend("#000", ICON_OVERLAY)
|
||||
for(var/atom/A in atoms)
|
||||
if(A)
|
||||
var/icon/img = getFlatIcon(A)
|
||||
if(istype(img, /icon))
|
||||
if(istype(A, /mob/living) && A:lying)
|
||||
img.BecomeLying()
|
||||
var/xoff = (A.x - tx) * 32
|
||||
var/yoff = (A.y - ty) * 32
|
||||
cap.Blend(img, blendMode2iconMode(A.blend_mode), A.pixel_x + xoff, A.pixel_y + yoff)
|
||||
|
||||
return cap
|
||||
|
||||
|
||||
@@ -610,13 +610,13 @@ proc/dd_sortedTextList(list/incoming)
|
||||
/datum/alarm/dd_SortValue()
|
||||
return "[sanitize_old(last_name)]"
|
||||
|
||||
/proc/subtypes(prototype)
|
||||
/proc/subtypesof(prototype)
|
||||
return (typesof(prototype) - prototype)
|
||||
|
||||
//creates every subtype of prototype (excluding prototype) and adds it to list L.
|
||||
//if no list/L is provided, one is created.
|
||||
/proc/init_subtypes(prototype, list/L)
|
||||
if(!istype(L)) L = list()
|
||||
for(var/path in subtypes(prototype))
|
||||
for(var/path in subtypesof(prototype))
|
||||
L += new path()
|
||||
return L
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
if (config.log_admin)
|
||||
diary << "\[[time_stamp()]]ADMIN: [text][log_end]"
|
||||
|
||||
|
||||
/proc/log_debug(text)
|
||||
if (config.log_debug)
|
||||
diary << "\[[time_stamp()]]DEBUG: [text][log_end]"
|
||||
@@ -34,7 +33,6 @@
|
||||
if(C.prefs.toggles & CHAT_DEBUGLOGS)
|
||||
C << "DEBUG: [text]"
|
||||
|
||||
|
||||
/proc/log_game(text)
|
||||
if (config.log_game)
|
||||
diary << "\[[time_stamp()]]GAME: [text][log_end]"
|
||||
@@ -79,6 +77,11 @@
|
||||
if (config.log_pda)
|
||||
diary << "\[[time_stamp()]]PDA: [text][log_end]"
|
||||
|
||||
/proc/log_to_dd(text)
|
||||
world.log << text //this comes before the config check because it can't possibly runtime
|
||||
if(config.log_world_output)
|
||||
diary << "\[[time_stamp()]]DD_OUTPUT: [text][log_end]"
|
||||
|
||||
/proc/log_misc(text)
|
||||
diary << "\[[time_stamp()]]MISC: [text][log_end]"
|
||||
|
||||
@@ -91,7 +94,7 @@
|
||||
if(dir & WEST) comps += "WEST"
|
||||
if(dir & UP) comps += "UP"
|
||||
if(dir & DOWN) comps += "DOWN"
|
||||
|
||||
|
||||
return english_list(comps, nothing_text="0", and_text="|", comma_text="|")
|
||||
|
||||
//more or less a logging utility
|
||||
|
||||
@@ -61,6 +61,13 @@ proc/random_facial_hair_style(gender, species = "Human")
|
||||
f_style = pick(valid_facialhairstyles)
|
||||
|
||||
return f_style
|
||||
|
||||
proc/sanitize_name(name, species = "Human")
|
||||
var/datum/species/current_species
|
||||
if(species)
|
||||
current_species = all_species[species]
|
||||
|
||||
return current_species ? current_species.sanitize_name(name) : sanitizeName(name)
|
||||
|
||||
proc/random_name(gender, species = "Human")
|
||||
|
||||
|
||||
86
code/_helpers/spawn_sync.dm
Normal file
86
code/_helpers/spawn_sync.dm
Normal file
@@ -0,0 +1,86 @@
|
||||
//-------------------------------
|
||||
/*
|
||||
Spawn sync helper
|
||||
|
||||
Helps syncronize spawn()ing multiple processes in loops.
|
||||
|
||||
Example for using this:
|
||||
|
||||
//Create new spawn_sync datum
|
||||
var/datum/spawn_sync/sync = new()
|
||||
|
||||
for(var/obj/O in list)
|
||||
//Call start_worker(), passing it first the object, then a string of the name of the proc you want called, then
|
||||
// any and all arguments you want passed to the proc.
|
||||
sync.start_worker(O, "do_something", arg1, arg2)
|
||||
|
||||
//Finally call wait_until_done()
|
||||
sync.wait_until_done()
|
||||
|
||||
//Once all the workers have completed, or the failsafe has triggered, the code will continue. By default the
|
||||
// failsafe is roughly 10 seconds (100 checks).
|
||||
*/
|
||||
//-------------------------------
|
||||
/datum/spawn_sync
|
||||
var/count = 1
|
||||
var/failsafe = 100 //how many checks before the failsafe triggers and the helper stops waiting
|
||||
|
||||
//Opens a thread counter
|
||||
/datum/spawn_sync/proc/open()
|
||||
count++
|
||||
|
||||
//Closes a thread counter
|
||||
/datum/spawn_sync/proc/close()
|
||||
count--
|
||||
|
||||
//Finalizes the spawn sync by removing the original starting count
|
||||
/datum/spawn_sync/proc/finalize()
|
||||
close()
|
||||
|
||||
//Resets the counter if you want to utilize the same datum multiple times
|
||||
// Optional: pass the number of checks you want for the failsafe
|
||||
/datum/spawn_sync/proc/reset(var/safety = 100)
|
||||
count = 1
|
||||
failsafe = safety
|
||||
|
||||
//Check if all threads have returned
|
||||
// Returns 0 if not all threads have completed
|
||||
// Returns 1 if all threads have completed
|
||||
/datum/spawn_sync/proc/check()
|
||||
safety_check()
|
||||
return count > 0 ? 1 : 0
|
||||
|
||||
//Failsafe in case something breaks horribly
|
||||
/datum/spawn_sync/proc/safety_check()
|
||||
failsafe--
|
||||
if(failsafe < 1)
|
||||
count = 0
|
||||
|
||||
//Set failsafe check count in case you need more time for the workers to return
|
||||
/datum/spawn_sync/proc/set_failsafe(var/safety)
|
||||
failsafe = safety
|
||||
|
||||
/datum/spawn_sync/proc/start_worker()
|
||||
//Extract the thread run proc and it's arguments from the variadic args list.
|
||||
ASSERT(args.len > 0)
|
||||
var/obj = args[1]
|
||||
var/thread_proc = args[2]
|
||||
|
||||
//dispatch a new thread
|
||||
open()
|
||||
spawn()
|
||||
//Utilise try/catch keywords here so the code continues even if an error occurs.
|
||||
try
|
||||
call(obj, thread_proc)(arglist(args.Copy(3)))
|
||||
catch(var/exception/e)
|
||||
error("[e] on [e.file]:[e.line]")
|
||||
close()
|
||||
|
||||
/datum/spawn_sync/proc/wait_until_done()
|
||||
finalize()
|
||||
|
||||
//Create a while loop to check if the sync is complete yet, it will return once all the spawn threads have
|
||||
// completed, or the failsafe has expired.
|
||||
while(check())
|
||||
//Add a sleep call to delay each check.
|
||||
sleep(1)
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#define isanimal(A) istype(A, /mob/living/simple_animal)
|
||||
|
||||
#define isairlock(A) istype(A, /obj/machinery/door/airlock)
|
||||
|
||||
#define isbrain(A) istype(A, /mob/living/carbon/brain)
|
||||
|
||||
#define iscarbon(A) istype(A, /mob/living/carbon)
|
||||
|
||||
@@ -255,6 +255,8 @@ datum/hud/New(mob/owner)
|
||||
|
||||
if(ishuman(mymob))
|
||||
human_hud(ui_style, ui_color, ui_alpha, mymob) // Pass the player the UI style chosen in preferences
|
||||
else if(isrobot(mymob))
|
||||
robot_hud()
|
||||
else if(issmall(mymob))
|
||||
monkey_hud(ui_style, ui_color, ui_alpha)
|
||||
else if(isbrain(mymob))
|
||||
@@ -265,8 +267,6 @@ datum/hud/New(mob/owner)
|
||||
slime_hud()
|
||||
else if(isAI(mymob))
|
||||
ai_hud()
|
||||
else if(isrobot(mymob))
|
||||
robot_hud()
|
||||
else if(isobserver(mymob))
|
||||
ghost_hud()
|
||||
else
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// DM Environment file for ProcessScheduler.dme.
|
||||
// All manual changes should be made outside the BEGIN_ and END_ blocks.
|
||||
// New source code should be placed in .dm files: choose File/New --> Code File.
|
||||
|
||||
// BEGIN_INTERNALS
|
||||
// END_INTERNALS
|
||||
|
||||
// BEGIN_FILE_DIR
|
||||
#define FILE_DIR .
|
||||
// END_FILE_DIR
|
||||
|
||||
// BEGIN_PREFERENCES
|
||||
// END_PREFERENCES
|
||||
|
||||
// BEGIN_INCLUDE
|
||||
#include "core\_define.dm"
|
||||
#include "core\_stubs.dm"
|
||||
#include "core\process.dm"
|
||||
#include "core\processScheduler.dm"
|
||||
#include "core\updateQueue.dm"
|
||||
#include "core\updateQueueWorker.dm"
|
||||
#include "test\processSchedulerView.dm"
|
||||
#include "test\testDyingUpdateQueueProcess.dm"
|
||||
#include "test\testHarness.dm"
|
||||
#include "test\testHungProcess.dm"
|
||||
#include "test\testNiceProcess.dm"
|
||||
#include "test\testSlowProcess.dm"
|
||||
#include "test\testUpdateQueue.dm"
|
||||
#include "test\testUpdateQueueProcess.dm"
|
||||
#include "test\testZombieProcess.dm"
|
||||
// END_INCLUDE
|
||||
|
||||
@@ -4,15 +4,7 @@
|
||||
* This file contains constructs that the process scheduler expects to exist
|
||||
* in a standard ss13 fork.
|
||||
*/
|
||||
/*
|
||||
/**
|
||||
* message_admins
|
||||
*
|
||||
* sends a message to admins
|
||||
*/
|
||||
/proc/message_admins(msg)
|
||||
world << msg
|
||||
*/
|
||||
|
||||
/**
|
||||
* logTheThing
|
||||
*
|
||||
@@ -25,14 +17,3 @@
|
||||
world << "Diary: \[[diaryType]:[type]] [text]"
|
||||
else
|
||||
world << "Log: \[[type]] [text]"
|
||||
|
||||
/**
|
||||
* var/disposed
|
||||
*
|
||||
* In goonstation, disposed is set to 1 after an object enters the delete queue
|
||||
* or the object is placed in an object pool (effectively out-of-play so to speak)
|
||||
*/
|
||||
/datum/var/disposed
|
||||
// Garbage collection (controller).
|
||||
/datum/var/gcDestroyed
|
||||
/datum/var/timeDestroyed
|
||||
@@ -48,7 +48,7 @@
|
||||
// This controls how often the process will yield (call sleep(0)) while it is running.
|
||||
// Every concurrent process should sleep periodically while running in order to allow other
|
||||
// processes to execute concurrently.
|
||||
var/tmp/sleep_interval = PROCESS_DEFAULT_SLEEP_INTERVAL
|
||||
var/tmp/sleep_interval
|
||||
|
||||
// hang_warning_time - this is the time (in 1/10 seconds) after which the server will begin to show "maybe hung" in the context window
|
||||
var/tmp/hang_warning_time = PROCESS_DEFAULT_HANG_WARNING_TIME
|
||||
@@ -59,20 +59,20 @@
|
||||
// hang_restart_time - After this much time(in 1/10 seconds), the server will automatically kill and restart the process.
|
||||
var/tmp/hang_restart_time = PROCESS_DEFAULT_HANG_RESTART_TIME
|
||||
|
||||
// cpu_threshold - if world.cpu >= cpu_threshold, scheck() will call sleep(1) to defer further work until the next tick. This keeps a process from driving a tick into overtime (causing perceptible lag)
|
||||
var/tmp/cpu_threshold = PROCESS_DEFAULT_CPU_THRESHOLD
|
||||
|
||||
// How many times in the current run has the process deferred work till the next tick?
|
||||
var/tmp/cpu_defer_count = 0
|
||||
|
||||
// How many SCHECKs have been skipped (to limit btime calls)
|
||||
var/tmp/calls_since_last_scheck = 0
|
||||
|
||||
/**
|
||||
* recordkeeping vars
|
||||
*/
|
||||
|
||||
// Records the time (server ticks) at which the process last finished sleeping
|
||||
// Records the time (1/10s timeofday) at which the process last finished sleeping
|
||||
var/tmp/last_slept = 0
|
||||
|
||||
// Records the time (s-ticks) at which the process last began running
|
||||
// Records the time (1/10s timeofday) at which the process last began running
|
||||
var/tmp/run_start = 0
|
||||
|
||||
// Records the number of times this process has been killed and restarted
|
||||
@@ -85,26 +85,33 @@
|
||||
|
||||
var/tmp/last_object
|
||||
|
||||
datum/controller/process/New(var/datum/controller/processScheduler/scheduler)
|
||||
// Counts the number of times an exception has occurred; gets reset after 10
|
||||
var/tmp/list/exceptions = list()
|
||||
|
||||
// Number of deciseconds to delay before starting the process
|
||||
var/start_delay = 0
|
||||
|
||||
/datum/controller/process/New(var/datum/controller/processScheduler/scheduler)
|
||||
..()
|
||||
main = scheduler
|
||||
previousStatus = "idle"
|
||||
idle()
|
||||
name = "process"
|
||||
schedule_interval = 50
|
||||
sleep_interval = 2
|
||||
sleep_interval = world.tick_lag / PROCESS_DEFAULT_SLEEP_INTERVAL
|
||||
last_slept = 0
|
||||
run_start = 0
|
||||
ticks = 0
|
||||
last_task = 0
|
||||
last_object = null
|
||||
|
||||
datum/controller/process/proc/started()
|
||||
/datum/controller/process/proc/started()
|
||||
var/timeofhour = TimeOfHour
|
||||
// Initialize last_slept so we can know when to sleep
|
||||
last_slept = world.timeofday
|
||||
last_slept = timeofhour
|
||||
|
||||
// Initialize run_start so we can detect hung processes.
|
||||
run_start = world.timeofday
|
||||
run_start = timeofhour
|
||||
|
||||
// Initialize defer count
|
||||
cpu_defer_count = 0
|
||||
@@ -114,65 +121,65 @@ datum/controller/process/proc/started()
|
||||
|
||||
onStart()
|
||||
|
||||
datum/controller/process/proc/finished()
|
||||
/datum/controller/process/proc/finished()
|
||||
ticks++
|
||||
idle()
|
||||
main.processFinished(src)
|
||||
|
||||
onFinish()
|
||||
|
||||
datum/controller/process/proc/doWork()
|
||||
/datum/controller/process/proc/doWork()
|
||||
|
||||
datum/controller/process/proc/setup()
|
||||
/datum/controller/process/proc/setup()
|
||||
|
||||
datum/controller/process/proc/process()
|
||||
/datum/controller/process/proc/process()
|
||||
started()
|
||||
doWork()
|
||||
finished()
|
||||
|
||||
datum/controller/process/proc/running()
|
||||
/datum/controller/process/proc/running()
|
||||
idle = 0
|
||||
queued = 0
|
||||
running = 1
|
||||
hung = 0
|
||||
setStatus(PROCESS_STATUS_RUNNING)
|
||||
|
||||
datum/controller/process/proc/idle()
|
||||
/datum/controller/process/proc/idle()
|
||||
queued = 0
|
||||
running = 0
|
||||
idle = 1
|
||||
hung = 0
|
||||
setStatus(PROCESS_STATUS_IDLE)
|
||||
|
||||
datum/controller/process/proc/queued()
|
||||
/datum/controller/process/proc/queued()
|
||||
idle = 0
|
||||
running = 0
|
||||
queued = 1
|
||||
hung = 0
|
||||
setStatus(PROCESS_STATUS_QUEUED)
|
||||
|
||||
datum/controller/process/proc/hung()
|
||||
/datum/controller/process/proc/hung()
|
||||
hung = 1
|
||||
setStatus(PROCESS_STATUS_HUNG)
|
||||
|
||||
datum/controller/process/proc/handleHung()
|
||||
/datum/controller/process/proc/handleHung()
|
||||
var/timeofhour = TimeOfHour
|
||||
var/datum/lastObj = last_object
|
||||
var/lastObjType = "null"
|
||||
if(istype(lastObj))
|
||||
lastObjType = lastObj.type
|
||||
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if (world.timeofday < run_start)
|
||||
run_start -= 864000
|
||||
|
||||
var/msg = "[name] process hung at tick #[ticks]. Process was unresponsive for [(world.timeofday - run_start) / 10] seconds and was restarted. Last task: [last_task]. Last Object Type: [lastObjType]"
|
||||
// If timeofhour has rolled over, then we need to adjust.
|
||||
if (timeofhour < run_start)
|
||||
run_start -= 36000
|
||||
var/msg = "[name] process hung at tick #[ticks]. Process was unresponsive for [(timeofhour - run_start) / 10] seconds and was restarted. Last task: [last_task]. Last Object Type: [lastObjType]"
|
||||
logTheThing("debug", null, null, msg)
|
||||
logTheThing("diary", null, null, msg, "debug")
|
||||
message_admins(msg)
|
||||
|
||||
main.restartProcess(src.name)
|
||||
|
||||
datum/controller/process/proc/kill()
|
||||
/datum/controller/process/proc/kill()
|
||||
if (!killed)
|
||||
var/msg = "[name] process was killed at tick #[ticks]."
|
||||
logTheThing("debug", null, null, msg)
|
||||
@@ -182,59 +189,68 @@ datum/controller/process/proc/kill()
|
||||
// Allow inheritors to clean up if needed
|
||||
onKill()
|
||||
|
||||
killed = TRUE
|
||||
// This should del
|
||||
del(src)
|
||||
|
||||
del(src) // This should del
|
||||
|
||||
datum/controller/process/proc/scheck(var/tickId = 0)
|
||||
// Do not call this directly - use SHECK or SCHECK_EVERY
|
||||
/datum/controller/process/proc/sleepCheck(var/tickId = 0)
|
||||
calls_since_last_scheck = 0
|
||||
if (killed)
|
||||
// The kill proc is the only place where killed is set.
|
||||
// The kill proc should have deleted this datum, and all sleeping procs that are
|
||||
// owned by it.
|
||||
CRASH("A killed process is still running somehow...")
|
||||
if (hung)
|
||||
// This will only really help if the doWork proc ends up in an infinite loop.
|
||||
handleHung()
|
||||
CRASH("Process [name] hung and was restarted.")
|
||||
|
||||
// For each tick the process defers, it increments the cpu_defer_count so we don't
|
||||
// defer indefinitely
|
||||
if (world.cpu >= cpu_threshold + cpu_defer_count * 10)
|
||||
sleep(1)
|
||||
if (main.getCurrentTickElapsedTime() > main.timeAllowance)
|
||||
sleep(world.tick_lag)
|
||||
cpu_defer_count++
|
||||
last_slept = world.timeofday
|
||||
last_slept = TimeOfHour
|
||||
else
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if (world.timeofday < last_slept)
|
||||
last_slept -= 864000
|
||||
var/timeofhour = TimeOfHour
|
||||
// If timeofhour has rolled over, then we need to adjust.
|
||||
if (timeofhour < last_slept)
|
||||
last_slept -= 36000
|
||||
|
||||
if (world.timeofday > last_slept + sleep_interval)
|
||||
// If we haven't slept in sleep_interval ticks, sleep to allow other work to proceed.
|
||||
if (timeofhour > last_slept + sleep_interval)
|
||||
// If we haven't slept in sleep_interval deciseconds, sleep to allow other work to proceed.
|
||||
sleep(0)
|
||||
last_slept = world.timeofday
|
||||
last_slept = TimeOfHour
|
||||
|
||||
datum/controller/process/proc/update()
|
||||
/datum/controller/process/proc/update()
|
||||
// Clear delta
|
||||
if(previousStatus != status)
|
||||
setStatus(status)
|
||||
|
||||
var/elapsedTime = getElapsedTime()
|
||||
|
||||
if (elapsedTime > hang_restart_time)
|
||||
if (hung)
|
||||
handleHung()
|
||||
return
|
||||
else if (elapsedTime > hang_restart_time)
|
||||
hung()
|
||||
else if (elapsedTime > hang_alert_time)
|
||||
setStatus(PROCESS_STATUS_PROBABLY_HUNG)
|
||||
else if (elapsedTime > hang_warning_time)
|
||||
setStatus(PROCESS_STATUS_MAYBE_HUNG)
|
||||
|
||||
datum/controller/process/proc/getElapsedTime()
|
||||
if (world.timeofday < run_start)
|
||||
return world.timeofday - (run_start - 864000)
|
||||
return world.timeofday - run_start
|
||||
|
||||
datum/controller/process/proc/tickDetail()
|
||||
/datum/controller/process/proc/getElapsedTime()
|
||||
var/timeofhour = TimeOfHour
|
||||
if (timeofhour < run_start)
|
||||
return timeofhour - (run_start - 36000)
|
||||
return timeofhour - run_start
|
||||
|
||||
/datum/controller/process/proc/tickDetail()
|
||||
return
|
||||
|
||||
datum/controller/process/proc/getContext()
|
||||
/datum/controller/process/proc/getContext()
|
||||
return "<tr><td>[name]</td><td>[main.averageRunTime(src)]</td><td>[main.last_run_time[src]]</td><td>[main.highest_run_time[src]]</td><td>[ticks]</td></tr>\n"
|
||||
|
||||
datum/controller/process/proc/getContextData()
|
||||
/datum/controller/process/proc/getContextData()
|
||||
return list(
|
||||
"name" = name,
|
||||
"averageRunTime" = main.averageRunTime(src),
|
||||
@@ -246,10 +262,10 @@ datum/controller/process/proc/getContextData()
|
||||
"disabled" = disabled
|
||||
)
|
||||
|
||||
datum/controller/process/proc/getStatus()
|
||||
/datum/controller/process/proc/getStatus()
|
||||
return status
|
||||
|
||||
datum/controller/process/proc/getStatusText(var/s = 0)
|
||||
/datum/controller/process/proc/getStatusText(var/s = 0)
|
||||
if(!s)
|
||||
s = status
|
||||
switch(s)
|
||||
@@ -268,21 +284,21 @@ datum/controller/process/proc/getStatusText(var/s = 0)
|
||||
else
|
||||
return "UNKNOWN"
|
||||
|
||||
datum/controller/process/proc/getPreviousStatus()
|
||||
/datum/controller/process/proc/getPreviousStatus()
|
||||
return previousStatus
|
||||
|
||||
datum/controller/process/proc/getPreviousStatusText()
|
||||
/datum/controller/process/proc/getPreviousStatusText()
|
||||
return getStatusText(previousStatus)
|
||||
|
||||
datum/controller/process/proc/setStatus(var/newStatus)
|
||||
/datum/controller/process/proc/setStatus(var/newStatus)
|
||||
previousStatus = status
|
||||
status = newStatus
|
||||
|
||||
datum/controller/process/proc/setLastTask(var/task, var/object)
|
||||
/datum/controller/process/proc/setLastTask(var/task, var/object)
|
||||
last_task = task
|
||||
last_object = object
|
||||
|
||||
datum/controller/process/proc/_copyStateFrom(var/datum/controller/process/target)
|
||||
/datum/controller/process/proc/_copyStateFrom(var/datum/controller/process/target)
|
||||
main = target.main
|
||||
name = target.name
|
||||
schedule_interval = target.schedule_interval
|
||||
@@ -295,28 +311,62 @@ datum/controller/process/proc/_copyStateFrom(var/datum/controller/process/target
|
||||
last_object = target.last_object
|
||||
copyStateFrom(target)
|
||||
|
||||
datum/controller/process/proc/copyStateFrom(var/datum/controller/process/target)
|
||||
/datum/controller/process/proc/copyStateFrom(var/datum/controller/process/target)
|
||||
|
||||
datum/controller/process/proc/onKill()
|
||||
/datum/controller/process/proc/onKill()
|
||||
|
||||
datum/controller/process/proc/onStart()
|
||||
/datum/controller/process/proc/onStart()
|
||||
|
||||
datum/controller/process/proc/onFinish()
|
||||
/datum/controller/process/proc/onFinish()
|
||||
|
||||
datum/controller/process/proc/disable()
|
||||
/datum/controller/process/proc/disable()
|
||||
disabled = 1
|
||||
|
||||
datum/controller/process/proc/enable()
|
||||
/datum/controller/process/proc/enable()
|
||||
disabled = 0
|
||||
|
||||
/datum/controller/process/proc/getAverageRunTime()
|
||||
return main.averageRunTime(src)
|
||||
/datum/controller/process/proc/getLastRunTime()
|
||||
return main.getProcessLastRunTime(src)
|
||||
|
||||
/datum/controller/process/proc/getHighestRunTime()
|
||||
return main.getProcessHighestRunTime(src)
|
||||
|
||||
/datum/controller/process/proc/getTicks()
|
||||
return ticks
|
||||
|
||||
/datum/controller/process/proc/getStatName()
|
||||
return name
|
||||
/datum/controller/process/proc/statProcess()
|
||||
var/averageRunTime = round(getAverageRunTime(), 0.1)/10
|
||||
var/lastRunTime = round(getLastRunTime(), 0.1)/10
|
||||
var/highestRunTime = round(getHighestRunTime(), 0.1)/10
|
||||
stat("[name]", "T#[getTicks()] | AR [averageRunTime] | LR [lastRunTime] | HR [highestRunTime] | D [cpu_defer_count]")
|
||||
|
||||
/datum/controller/process/proc/getTickTime()
|
||||
return "#[getTicks()]\t- [getLastRunTime()]"
|
||||
/datum/controller/process/proc/catchException(var/exception/e, var/thrower)
|
||||
var/etext = "[e]"
|
||||
var/eid = "[e]" // Exception ID, for tracking repeated exceptions
|
||||
var/ptext = "" // "processing..." text, for what was being processed (if known)
|
||||
if(istype(e))
|
||||
etext += " in [e.file], line [e.line]"
|
||||
eid = "[e.file]:[e.line]"
|
||||
if(eid in exceptions)
|
||||
if(exceptions[eid]++ >= 10)
|
||||
return
|
||||
else
|
||||
exceptions[eid] = 1
|
||||
if(istype(thrower, /datum))
|
||||
var/datum/D = thrower
|
||||
ptext = " processing [D.type]"
|
||||
if(istype(thrower, /atom))
|
||||
var/atom/A = thrower
|
||||
ptext += " ([A]) ([A.x],[A.y],[A.z])"
|
||||
log_to_dd("\[[time_stamp()]\] Process [name] caught exception[ptext]: [etext]")
|
||||
if(exceptions[eid] >= 10)
|
||||
log_to_dd("This exception will now be ignored for ten minutes.")
|
||||
spawn(6000)
|
||||
exceptions[eid] = 0
|
||||
|
||||
/datum/controller/process/proc/catchBadType(var/datum/caught)
|
||||
if(isnull(caught) || !istype(caught) || !isnull(caught.gcDestroyed))
|
||||
return // Only bother with types we can identify and that don't belong
|
||||
catchException("Type [caught.type] does not belong in process' queue")
|
||||
|
||||
@@ -1,320 +1,382 @@
|
||||
// Singleton instance of game_controller_new, setup in world.New()
|
||||
var/global/datum/controller/processScheduler/processScheduler
|
||||
|
||||
/datum/controller/processScheduler
|
||||
// Processes known by the scheduler
|
||||
var/tmp/datum/controller/process/list/processes = new
|
||||
|
||||
// Processes that are currently running
|
||||
var/tmp/datum/controller/process/list/running = new
|
||||
|
||||
// Processes that are idle
|
||||
var/tmp/datum/controller/process/list/idle = new
|
||||
|
||||
// Processes that are queued to run
|
||||
var/tmp/datum/controller/process/list/queued = new
|
||||
|
||||
// Process name -> process object map
|
||||
var/tmp/datum/controller/process/list/nameToProcessMap = new
|
||||
|
||||
// Process last start times
|
||||
var/tmp/datum/controller/process/list/last_start = new
|
||||
|
||||
// Process last run durations
|
||||
var/tmp/datum/controller/process/list/last_run_time = new
|
||||
|
||||
// Per process list of the last 20 durations
|
||||
var/tmp/datum/controller/process/list/last_twenty_run_times = new
|
||||
|
||||
// Process highest run time
|
||||
var/tmp/datum/controller/process/list/highest_run_time = new
|
||||
|
||||
// Sleep 1 tick -- This may be too aggressive.
|
||||
var/tmp/scheduler_sleep_interval = 1
|
||||
|
||||
// Controls whether the scheduler is running or not
|
||||
var/tmp/isRunning = 0
|
||||
|
||||
// Setup for these processes will be deferred until all the other processes are set up.
|
||||
var/tmp/list/deferredSetupList = new
|
||||
|
||||
/**
|
||||
* deferSetupFor
|
||||
* @param path processPath
|
||||
* If a process needs to be initialized after everything else, add it to
|
||||
* the deferred setup list. On goonstation, only the ticker needs to have
|
||||
* this treatment.
|
||||
*/
|
||||
/datum/controller/processScheduler/proc/deferSetupFor(var/processPath)
|
||||
if (!(processPath in deferredSetupList))
|
||||
deferredSetupList += processPath
|
||||
|
||||
/datum/controller/processScheduler/proc/setup()
|
||||
// There can be only one
|
||||
if(processScheduler && (processScheduler != src))
|
||||
del(src)
|
||||
return 0
|
||||
|
||||
var/process
|
||||
// Add all the processes we can find, except for the ticker
|
||||
for (process in typesof(/datum/controller/process) - /datum/controller/process)
|
||||
if (!(process in deferredSetupList))
|
||||
addProcess(new process(src))
|
||||
|
||||
for (process in deferredSetupList)
|
||||
addProcess(new process(src))
|
||||
|
||||
/datum/controller/processScheduler/proc/start()
|
||||
isRunning = 1
|
||||
spawn(0)
|
||||
process()
|
||||
|
||||
/datum/controller/processScheduler/proc/process()
|
||||
while(isRunning)
|
||||
checkRunningProcesses()
|
||||
queueProcesses()
|
||||
runQueuedProcesses()
|
||||
sleep(scheduler_sleep_interval)
|
||||
|
||||
/datum/controller/processScheduler/proc/stop()
|
||||
isRunning = 0
|
||||
|
||||
/datum/controller/processScheduler/proc/checkRunningProcesses()
|
||||
for(var/datum/controller/process/p in running)
|
||||
p.update()
|
||||
|
||||
if (isnull(p)) // Process was killed
|
||||
continue
|
||||
|
||||
var/status = p.getStatus()
|
||||
var/previousStatus = p.getPreviousStatus()
|
||||
|
||||
// Check status changes
|
||||
if(status != previousStatus)
|
||||
//Status changed.
|
||||
|
||||
switch(status)
|
||||
if(PROCESS_STATUS_MAYBE_HUNG)
|
||||
message_admins("Process '[p.name]' is [p.getStatusText(status)].")
|
||||
if(PROCESS_STATUS_PROBABLY_HUNG)
|
||||
message_admins("Process '[p.name]' is [p.getStatusText(status)].")
|
||||
if(PROCESS_STATUS_HUNG)
|
||||
message_admins("Process '[p.name]' is [p.getStatusText(status)].")
|
||||
p.handleHung()
|
||||
|
||||
/datum/controller/processScheduler/proc/queueProcesses()
|
||||
for(var/datum/controller/process/p in processes)
|
||||
// Don't double-queue, don't queue running processes
|
||||
if (p.disabled || p.running || p.queued || !p.idle)
|
||||
continue
|
||||
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if (world.timeofday < last_start[p])
|
||||
last_start[p] -= 864000
|
||||
|
||||
// If the process should be running by now, go ahead and queue it
|
||||
if (world.timeofday > last_start[p] + p.schedule_interval)
|
||||
setQueuedProcessState(p)
|
||||
|
||||
/datum/controller/processScheduler/proc/runQueuedProcesses()
|
||||
for(var/datum/controller/process/p in queued)
|
||||
runProcess(p)
|
||||
|
||||
/datum/controller/processScheduler/proc/addProcess(var/datum/controller/process/process)
|
||||
processes.Add(process)
|
||||
process.idle()
|
||||
idle.Add(process)
|
||||
|
||||
// init recordkeeping vars
|
||||
last_start.Add(process)
|
||||
last_start[process] = 0
|
||||
last_run_time.Add(process)
|
||||
last_run_time[process] = 0
|
||||
last_twenty_run_times.Add(process)
|
||||
last_twenty_run_times[process] = list()
|
||||
highest_run_time.Add(process)
|
||||
highest_run_time[process] = 0
|
||||
|
||||
// init starts and stops record starts
|
||||
recordStart(process, 0)
|
||||
recordEnd(process, 0)
|
||||
|
||||
// Set up process
|
||||
process.setup()
|
||||
|
||||
// Save process in the name -> process map
|
||||
nameToProcessMap[process.name] = process
|
||||
|
||||
/datum/controller/processScheduler/proc/replaceProcess(var/datum/controller/process/oldProcess, var/datum/controller/process/newProcess)
|
||||
processes.Remove(oldProcess)
|
||||
processes.Add(newProcess)
|
||||
|
||||
newProcess.idle()
|
||||
idle.Remove(oldProcess)
|
||||
running.Remove(oldProcess)
|
||||
queued.Remove(oldProcess)
|
||||
idle.Add(newProcess)
|
||||
|
||||
last_start.Remove(oldProcess)
|
||||
last_start.Add(newProcess)
|
||||
last_start[newProcess] = 0
|
||||
|
||||
last_run_time.Add(newProcess)
|
||||
last_run_time[newProcess] = last_run_time[oldProcess]
|
||||
last_run_time.Remove(oldProcess)
|
||||
|
||||
last_twenty_run_times.Add(newProcess)
|
||||
last_twenty_run_times[newProcess] = last_twenty_run_times[oldProcess]
|
||||
last_twenty_run_times.Remove(oldProcess)
|
||||
|
||||
highest_run_time.Add(newProcess)
|
||||
highest_run_time[newProcess] = highest_run_time[oldProcess]
|
||||
highest_run_time.Remove(oldProcess)
|
||||
|
||||
recordStart(newProcess, 0)
|
||||
recordEnd(newProcess, 0)
|
||||
|
||||
nameToProcessMap[newProcess.name] = newProcess
|
||||
|
||||
|
||||
/datum/controller/processScheduler/proc/runProcess(var/datum/controller/process/process)
|
||||
spawn(0)
|
||||
process.process()
|
||||
|
||||
/datum/controller/processScheduler/proc/processStarted(var/datum/controller/process/process)
|
||||
setRunningProcessState(process)
|
||||
recordStart(process)
|
||||
|
||||
/datum/controller/processScheduler/proc/processFinished(var/datum/controller/process/process)
|
||||
setIdleProcessState(process)
|
||||
recordEnd(process)
|
||||
|
||||
/datum/controller/processScheduler/proc/setIdleProcessState(var/datum/controller/process/process)
|
||||
if (process in running)
|
||||
running -= process
|
||||
if (process in queued)
|
||||
queued -= process
|
||||
if (!(process in idle))
|
||||
idle += process
|
||||
|
||||
process.idle()
|
||||
|
||||
/datum/controller/processScheduler/proc/setQueuedProcessState(var/datum/controller/process/process)
|
||||
if (process in running)
|
||||
running -= process
|
||||
if (process in idle)
|
||||
idle -= process
|
||||
if (!(process in queued))
|
||||
queued += process
|
||||
|
||||
// The other state transitions are handled internally by the process.
|
||||
process.queued()
|
||||
|
||||
/datum/controller/processScheduler/proc/setRunningProcessState(var/datum/controller/process/process)
|
||||
if (process in queued)
|
||||
queued -= process
|
||||
if (process in idle)
|
||||
idle -= process
|
||||
if (!(process in running))
|
||||
running += process
|
||||
|
||||
process.running()
|
||||
|
||||
/datum/controller/processScheduler/proc/recordStart(var/datum/controller/process/process, var/time = null)
|
||||
if (isnull(time))
|
||||
time = world.timeofday
|
||||
|
||||
last_start[process] = time
|
||||
|
||||
/datum/controller/processScheduler/proc/recordEnd(var/datum/controller/process/process, var/time = null)
|
||||
if (isnull(time))
|
||||
time = world.timeofday
|
||||
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if (time < last_start[process])
|
||||
last_start[process] -= 864000
|
||||
|
||||
var/lastRunTime = time - last_start[process]
|
||||
|
||||
if(lastRunTime < 0)
|
||||
lastRunTime = 0
|
||||
|
||||
recordRunTime(process, lastRunTime)
|
||||
|
||||
/**
|
||||
* recordRunTime
|
||||
* Records a run time for a process
|
||||
*/
|
||||
/datum/controller/processScheduler/proc/recordRunTime(var/datum/controller/process/process, time)
|
||||
last_run_time[process] = time
|
||||
if(time > highest_run_time[process])
|
||||
highest_run_time[process] = time
|
||||
|
||||
var/list/lastTwenty = last_twenty_run_times[process]
|
||||
if (lastTwenty.len == 20)
|
||||
lastTwenty.Cut(1, 2)
|
||||
lastTwenty.len++
|
||||
lastTwenty[lastTwenty.len] = time
|
||||
|
||||
/**
|
||||
* averageRunTime
|
||||
* returns the average run time (over the last 20) of the process
|
||||
*/
|
||||
/datum/controller/processScheduler/proc/averageRunTime(var/datum/controller/process/process)
|
||||
var/lastTwenty = last_twenty_run_times[process]
|
||||
|
||||
var/t = 0
|
||||
var/c = 0
|
||||
for(var/time in lastTwenty)
|
||||
t += time
|
||||
c++
|
||||
|
||||
if(c > 0)
|
||||
return t / c
|
||||
return c
|
||||
|
||||
/datum/controller/processScheduler/proc/getStatusData()
|
||||
var/list/data = new
|
||||
|
||||
for (var/datum/controller/process/p in processes)
|
||||
data.len++
|
||||
data[data.len] = p.getContextData()
|
||||
|
||||
return data
|
||||
|
||||
/datum/controller/processScheduler/proc/getProcessCount()
|
||||
return processes.len
|
||||
|
||||
/datum/controller/processScheduler/proc/hasProcess(var/processName as text)
|
||||
if (nameToProcessMap[processName])
|
||||
return 1
|
||||
|
||||
/datum/controller/processScheduler/proc/killProcess(var/processName as text)
|
||||
restartProcess(processName)
|
||||
|
||||
/datum/controller/processScheduler/proc/restartProcess(var/processName as text)
|
||||
if (hasProcess(processName))
|
||||
var/datum/controller/process/oldInstance = nameToProcessMap[processName]
|
||||
var/datum/controller/process/newInstance = new oldInstance.type(src)
|
||||
newInstance._copyStateFrom(oldInstance)
|
||||
replaceProcess(oldInstance, newInstance)
|
||||
oldInstance.kill()
|
||||
|
||||
/datum/controller/processScheduler/proc/enableProcess(var/processName as text)
|
||||
if (hasProcess(processName))
|
||||
var/datum/controller/process/process = nameToProcessMap[processName]
|
||||
process.enable()
|
||||
|
||||
/datum/controller/processScheduler/proc/disableProcess(var/processName as text)
|
||||
if (hasProcess(processName))
|
||||
var/datum/controller/process/process = nameToProcessMap[processName]
|
||||
process.disable()
|
||||
|
||||
/datum/controller/processScheduler/proc/getProcess(var/name)
|
||||
return nameToProcessMap[name]
|
||||
|
||||
/datum/controller/processScheduler/proc/getProcessLastRunTime(var/datum/controller/process/process)
|
||||
return last_run_time[process]
|
||||
|
||||
/datum/controller/processScheduler/proc/getIsRunning()
|
||||
return isRunning
|
||||
// Singleton instance of game_controller_new, setup in world.New()
|
||||
var/global/datum/controller/processScheduler/processScheduler
|
||||
|
||||
/datum/controller/processScheduler
|
||||
// Processes known by the scheduler
|
||||
var/tmp/datum/controller/process/list/processes = new
|
||||
|
||||
// Processes that are currently running
|
||||
var/tmp/datum/controller/process/list/running = new
|
||||
|
||||
// Processes that are idle
|
||||
var/tmp/datum/controller/process/list/idle = new
|
||||
|
||||
// Processes that are queued to run
|
||||
var/tmp/datum/controller/process/list/queued = new
|
||||
|
||||
// Process name -> process object map
|
||||
var/tmp/datum/controller/process/list/nameToProcessMap = new
|
||||
|
||||
// Process last queued times (world time)
|
||||
var/tmp/datum/controller/process/list/last_queued = new
|
||||
|
||||
// Process last start times (real time)
|
||||
var/tmp/datum/controller/process/list/last_start = new
|
||||
|
||||
// Process last run durations
|
||||
var/tmp/datum/controller/process/list/last_run_time = new
|
||||
|
||||
// Per process list of the last 20 durations
|
||||
var/tmp/datum/controller/process/list/last_twenty_run_times = new
|
||||
|
||||
// Process highest run time
|
||||
var/tmp/datum/controller/process/list/highest_run_time = new
|
||||
|
||||
// How long to sleep between runs (set to tick_lag in New)
|
||||
var/tmp/scheduler_sleep_interval
|
||||
|
||||
// Controls whether the scheduler is running or not
|
||||
var/tmp/isRunning = 0
|
||||
|
||||
// Setup for these processes will be deferred until all the other processes are set up.
|
||||
var/tmp/list/deferredSetupList = new
|
||||
|
||||
var/tmp/currentTick = 0
|
||||
|
||||
var/tmp/currentTickStart = 0
|
||||
|
||||
var/tmp/timeAllowance = 0
|
||||
|
||||
var/tmp/cpuAverage = 0
|
||||
|
||||
var/tmp/timeAllowanceMax = 0
|
||||
|
||||
/datum/controller/processScheduler/New()
|
||||
..()
|
||||
// When the process scheduler is first new'd, tick_lag may be wrong, so these
|
||||
// get re-initialized when the process scheduler is started.
|
||||
// (These are kept here for any processes that decide to process before round start)
|
||||
scheduler_sleep_interval = world.tick_lag
|
||||
timeAllowance = world.tick_lag * 0.5
|
||||
timeAllowanceMax = world.tick_lag
|
||||
|
||||
/**
|
||||
* deferSetupFor
|
||||
* @param path processPath
|
||||
* If a process needs to be initialized after everything else, add it to
|
||||
* the deferred setup list. On goonstation, only the ticker needs to have
|
||||
* this treatment.
|
||||
*/
|
||||
/datum/controller/processScheduler/proc/deferSetupFor(var/processPath)
|
||||
if (!(processPath in deferredSetupList))
|
||||
deferredSetupList += processPath
|
||||
|
||||
/datum/controller/processScheduler/proc/setup()
|
||||
// There can be only one
|
||||
if(processScheduler && (processScheduler != src))
|
||||
del(src)
|
||||
return 0
|
||||
|
||||
var/process
|
||||
// Add all the processes we can find, except for the ticker
|
||||
for (process in subtypesof(/datum/controller/process))
|
||||
if (!(process in deferredSetupList))
|
||||
addProcess(new process(src))
|
||||
|
||||
for (process in deferredSetupList)
|
||||
addProcess(new process(src))
|
||||
|
||||
/datum/controller/processScheduler/proc/start()
|
||||
isRunning = 1
|
||||
// tick_lag will have been set by now, so re-initialize these
|
||||
scheduler_sleep_interval = world.tick_lag
|
||||
timeAllowance = world.tick_lag * 0.5
|
||||
timeAllowanceMax = world.tick_lag
|
||||
updateStartDelays()
|
||||
spawn(0)
|
||||
process()
|
||||
|
||||
/datum/controller/processScheduler/proc/process()
|
||||
updateCurrentTickData()
|
||||
|
||||
for(var/i=world.tick_lag,i<world.tick_lag*50,i+=world.tick_lag)
|
||||
spawn(i) updateCurrentTickData()
|
||||
while(isRunning)
|
||||
// Hopefully spawning this for 50 ticks in the future will make it the first thing in the queue.
|
||||
spawn(world.tick_lag*50) updateCurrentTickData()
|
||||
checkRunningProcesses()
|
||||
queueProcesses()
|
||||
runQueuedProcesses()
|
||||
sleep(scheduler_sleep_interval)
|
||||
|
||||
/datum/controller/processScheduler/proc/stop()
|
||||
isRunning = 0
|
||||
|
||||
/datum/controller/processScheduler/proc/checkRunningProcesses()
|
||||
for(var/datum/controller/process/p in running)
|
||||
p.update()
|
||||
|
||||
if (isnull(p)) // Process was killed
|
||||
continue
|
||||
|
||||
var/status = p.getStatus()
|
||||
var/previousStatus = p.getPreviousStatus()
|
||||
|
||||
// Check status changes
|
||||
if(status != previousStatus)
|
||||
//Status changed.
|
||||
switch(status)
|
||||
if(PROCESS_STATUS_PROBABLY_HUNG)
|
||||
message_admins("Process '[p.name]' may be hung.")
|
||||
if(PROCESS_STATUS_HUNG)
|
||||
message_admins("Process '[p.name]' is hung and will be restarted.")
|
||||
|
||||
/datum/controller/processScheduler/proc/queueProcesses()
|
||||
for(var/datum/controller/process/p in processes)
|
||||
// Don't double-queue, don't queue running processes
|
||||
if (p.disabled || p.running || p.queued || !p.idle)
|
||||
continue
|
||||
|
||||
// If the process should be running by now, go ahead and queue it
|
||||
if (world.time >= last_queued[p] + p.schedule_interval)
|
||||
setQueuedProcessState(p)
|
||||
|
||||
/datum/controller/processScheduler/proc/runQueuedProcesses()
|
||||
for(var/datum/controller/process/p in queued)
|
||||
runProcess(p)
|
||||
|
||||
/datum/controller/processScheduler/proc/addProcess(var/datum/controller/process/process)
|
||||
processes.Add(process)
|
||||
process.idle()
|
||||
idle.Add(process)
|
||||
|
||||
// init recordkeeping vars
|
||||
last_start.Add(process)
|
||||
last_start[process] = 0
|
||||
last_run_time.Add(process)
|
||||
last_run_time[process] = 0
|
||||
last_twenty_run_times.Add(process)
|
||||
last_twenty_run_times[process] = list()
|
||||
highest_run_time.Add(process)
|
||||
highest_run_time[process] = 0
|
||||
|
||||
// init starts and stops record starts
|
||||
recordStart(process, 0)
|
||||
recordEnd(process, 0)
|
||||
|
||||
// Set up process
|
||||
process.setup()
|
||||
|
||||
// Save process in the name -> process map
|
||||
nameToProcessMap[process.name] = process
|
||||
|
||||
/datum/controller/processScheduler/proc/replaceProcess(var/datum/controller/process/oldProcess, var/datum/controller/process/newProcess)
|
||||
processes.Remove(oldProcess)
|
||||
processes.Add(newProcess)
|
||||
|
||||
newProcess.idle()
|
||||
idle.Remove(oldProcess)
|
||||
running.Remove(oldProcess)
|
||||
queued.Remove(oldProcess)
|
||||
idle.Add(newProcess)
|
||||
|
||||
last_start.Remove(oldProcess)
|
||||
last_start.Add(newProcess)
|
||||
last_start[newProcess] = 0
|
||||
|
||||
last_run_time.Add(newProcess)
|
||||
last_run_time[newProcess] = last_run_time[oldProcess]
|
||||
last_run_time.Remove(oldProcess)
|
||||
|
||||
last_twenty_run_times.Add(newProcess)
|
||||
last_twenty_run_times[newProcess] = last_twenty_run_times[oldProcess]
|
||||
last_twenty_run_times.Remove(oldProcess)
|
||||
|
||||
highest_run_time.Add(newProcess)
|
||||
highest_run_time[newProcess] = highest_run_time[oldProcess]
|
||||
highest_run_time.Remove(oldProcess)
|
||||
|
||||
recordStart(newProcess, 0)
|
||||
recordEnd(newProcess, 0)
|
||||
|
||||
nameToProcessMap[newProcess.name] = newProcess
|
||||
|
||||
/datum/controller/processScheduler/proc/updateStartDelays()
|
||||
for(var/datum/controller/process/p in processes)
|
||||
if(p.start_delay)
|
||||
last_queued[p] = world.time - p.start_delay
|
||||
|
||||
/datum/controller/processScheduler/proc/runProcess(var/datum/controller/process/process)
|
||||
spawn(0)
|
||||
process.process()
|
||||
|
||||
/datum/controller/processScheduler/proc/processStarted(var/datum/controller/process/process)
|
||||
setRunningProcessState(process)
|
||||
recordStart(process)
|
||||
|
||||
/datum/controller/processScheduler/proc/processFinished(var/datum/controller/process/process)
|
||||
setIdleProcessState(process)
|
||||
recordEnd(process)
|
||||
|
||||
/datum/controller/processScheduler/proc/setIdleProcessState(var/datum/controller/process/process)
|
||||
if (process in running)
|
||||
running -= process
|
||||
if (process in queued)
|
||||
queued -= process
|
||||
if (!(process in idle))
|
||||
idle += process
|
||||
|
||||
/datum/controller/processScheduler/proc/setQueuedProcessState(var/datum/controller/process/process)
|
||||
if (process in running)
|
||||
running -= process
|
||||
if (process in idle)
|
||||
idle -= process
|
||||
if (!(process in queued))
|
||||
queued += process
|
||||
|
||||
// The other state transitions are handled internally by the process.
|
||||
process.queued()
|
||||
|
||||
/datum/controller/processScheduler/proc/setRunningProcessState(var/datum/controller/process/process)
|
||||
if (process in queued)
|
||||
queued -= process
|
||||
if (process in idle)
|
||||
idle -= process
|
||||
if (!(process in running))
|
||||
running += process
|
||||
|
||||
/datum/controller/processScheduler/proc/recordStart(var/datum/controller/process/process, var/time = null)
|
||||
if (isnull(time))
|
||||
time = TimeOfHour
|
||||
last_queued[process] = world.time
|
||||
last_start[process] = time
|
||||
else
|
||||
last_queued[process] = (time == 0 ? 0 : world.time)
|
||||
last_start[process] = time
|
||||
|
||||
/datum/controller/processScheduler/proc/recordEnd(var/datum/controller/process/process, var/time = null)
|
||||
if (isnull(time))
|
||||
time = TimeOfHour
|
||||
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if (time < last_start[process])
|
||||
last_start[process] -= 36000
|
||||
|
||||
var/lastRunTime = time - last_start[process]
|
||||
|
||||
if(lastRunTime < 0)
|
||||
lastRunTime = 0
|
||||
|
||||
recordRunTime(process, lastRunTime)
|
||||
|
||||
/**
|
||||
* recordRunTime
|
||||
* Records a run time for a process
|
||||
*/
|
||||
/datum/controller/processScheduler/proc/recordRunTime(var/datum/controller/process/process, time)
|
||||
last_run_time[process] = time
|
||||
if(time > highest_run_time[process])
|
||||
highest_run_time[process] = time
|
||||
|
||||
var/list/lastTwenty = last_twenty_run_times[process]
|
||||
if (lastTwenty.len == 20)
|
||||
lastTwenty.Cut(1, 2)
|
||||
lastTwenty.len++
|
||||
lastTwenty[lastTwenty.len] = time
|
||||
|
||||
/**
|
||||
* averageRunTime
|
||||
* returns the average run time (over the last 20) of the process
|
||||
*/
|
||||
/datum/controller/processScheduler/proc/averageRunTime(var/datum/controller/process/process)
|
||||
var/lastTwenty = last_twenty_run_times[process]
|
||||
|
||||
var/t = 0
|
||||
var/c = 0
|
||||
for(var/time in lastTwenty)
|
||||
t += time
|
||||
c++
|
||||
|
||||
if(c > 0)
|
||||
return t / c
|
||||
return c
|
||||
|
||||
/datum/controller/processScheduler/proc/getProcessLastRunTime(var/datum/controller/process/process)
|
||||
return last_run_time[process]
|
||||
|
||||
/datum/controller/processScheduler/proc/getProcessHighestRunTime(var/datum/controller/process/process)
|
||||
return highest_run_time[process]
|
||||
|
||||
/datum/controller/processScheduler/proc/getStatusData()
|
||||
var/list/data = new
|
||||
|
||||
for (var/datum/controller/process/p in processes)
|
||||
data.len++
|
||||
data[data.len] = p.getContextData()
|
||||
|
||||
return data
|
||||
|
||||
/datum/controller/processScheduler/proc/getProcessCount()
|
||||
return processes.len
|
||||
|
||||
/datum/controller/processScheduler/proc/hasProcess(var/processName as text)
|
||||
if (nameToProcessMap[processName])
|
||||
return 1
|
||||
|
||||
/datum/controller/processScheduler/proc/killProcess(var/processName as text)
|
||||
restartProcess(processName)
|
||||
|
||||
/datum/controller/processScheduler/proc/restartProcess(var/processName as text)
|
||||
if (hasProcess(processName))
|
||||
var/datum/controller/process/oldInstance = nameToProcessMap[processName]
|
||||
var/datum/controller/process/newInstance = new oldInstance.type(src)
|
||||
newInstance._copyStateFrom(oldInstance)
|
||||
replaceProcess(oldInstance, newInstance)
|
||||
oldInstance.kill()
|
||||
|
||||
/datum/controller/processScheduler/proc/enableProcess(var/processName as text)
|
||||
if (hasProcess(processName))
|
||||
var/datum/controller/process/process = nameToProcessMap[processName]
|
||||
process.enable()
|
||||
|
||||
/datum/controller/processScheduler/proc/disableProcess(var/processName as text)
|
||||
if (hasProcess(processName))
|
||||
var/datum/controller/process/process = nameToProcessMap[processName]
|
||||
process.disable()
|
||||
|
||||
/datum/controller/processScheduler/proc/getCurrentTickElapsedTime()
|
||||
if (world.time > currentTick)
|
||||
updateCurrentTickData()
|
||||
return 0
|
||||
else
|
||||
return TimeOfHour - currentTickStart
|
||||
|
||||
/datum/controller/processScheduler/proc/updateCurrentTickData()
|
||||
if (world.time > currentTick)
|
||||
// New tick!
|
||||
currentTick = world.time
|
||||
currentTickStart = TimeOfHour
|
||||
updateTimeAllowance()
|
||||
cpuAverage = (world.cpu + cpuAverage + cpuAverage) / 3
|
||||
|
||||
/datum/controller/processScheduler/proc/updateTimeAllowance()
|
||||
// Time allowance goes down linearly with world.cpu.
|
||||
var/tmp/error = cpuAverage - 100
|
||||
var/tmp/timeAllowanceDelta = sign(error) * -0.5 * world.tick_lag * max(0, 0.001 * abs(error))
|
||||
|
||||
//timeAllowance = world.tick_lag * min(1, 0.5 * ((200/max(1,cpuAverage)) - 1))
|
||||
timeAllowance = min(timeAllowanceMax, max(0, timeAllowance + timeAllowanceDelta))
|
||||
|
||||
/datum/controller/processScheduler/proc/sign(var/x)
|
||||
if (x == 0)
|
||||
return 1
|
||||
return x / abs(x)
|
||||
|
||||
/datum/controller/processScheduler/proc/statProcesses()
|
||||
if(!isRunning)
|
||||
stat("Processes", "Scheduler not running")
|
||||
return
|
||||
stat("Processes", "[processes.len] (R [running.len] / Q [queued.len] / I [idle.len])")
|
||||
stat(null, "[round(cpuAverage, 0.1)] CPU, [round(timeAllowance, 0.1)/10] TA")
|
||||
for(var/datum/controller/process/p in processes)
|
||||
p.statProcess()
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* updateQueue.dm
|
||||
*/
|
||||
|
||||
#ifdef UPDATE_QUEUE_DEBUG
|
||||
#define uq_dbg(text) world << text
|
||||
#else
|
||||
#define uq_dbg(text)
|
||||
#endif
|
||||
/datum/updateQueue
|
||||
var/tmp/list/objects
|
||||
var/tmp/previousStart
|
||||
var/tmp/procName
|
||||
var/tmp/list/arguments
|
||||
var/tmp/datum/updateQueueWorker/currentWorker
|
||||
var/tmp/workerTimeout
|
||||
var/tmp/adjustedWorkerTimeout
|
||||
var/tmp/currentKillCount
|
||||
var/tmp/totalKillCount
|
||||
|
||||
/datum/updateQueue/New(list/objects = list(), procName = "update", list/arguments = list(), workerTimeout = 2, inplace = 0)
|
||||
..()
|
||||
|
||||
uq_dbg("Update queue created.")
|
||||
|
||||
// Init proc allows for recycling the worker.
|
||||
init(objects = objects, procName = procName, arguments = arguments, workerTimeout = workerTimeout, inplace = inplace)
|
||||
|
||||
/**
|
||||
* init
|
||||
* @param list objects objects to update
|
||||
* @param text procName the proc to call on each item in the object list
|
||||
* @param list arguments optional arguments to pass to the update proc
|
||||
* @param number workerTimeout number of ticks to wait for an update to
|
||||
finish before forking a new update worker
|
||||
* @param bool inplace whether the updateQueue should make a copy of objects.
|
||||
the internal list will be modified, so it is usually
|
||||
a good idea to leave this alone. Default behavior is to
|
||||
copy.
|
||||
*/
|
||||
/datum/updateQueue/proc/init(list/objects = list(), procName = "update", list/arguments = list(), workerTimeout = 2, inplace = 0)
|
||||
uq_dbg("Update queue initialization started.")
|
||||
|
||||
if (!inplace)
|
||||
// Make an internal copy of the list so we're not modifying the original.
|
||||
initList(objects)
|
||||
else
|
||||
src.objects = objects
|
||||
|
||||
// Init vars
|
||||
src.procName = procName
|
||||
src.arguments = arguments
|
||||
src.workerTimeout = workerTimeout
|
||||
|
||||
adjustedWorkerTimeout = workerTimeout
|
||||
currentKillCount = 0
|
||||
totalKillCount = 0
|
||||
|
||||
uq_dbg("Update queue initialization finished. procName = '[procName]'")
|
||||
|
||||
/datum/updateQueue/proc/initList(list/toCopy)
|
||||
/**
|
||||
* We will copy the list in reverse order, as our doWork proc
|
||||
* will access them by popping an element off the end of the list.
|
||||
* This ends up being quite a lot faster than taking elements off
|
||||
* the head of the list.
|
||||
*/
|
||||
objects = new
|
||||
|
||||
uq_dbg("Copying [toCopy.len] items for processing.")
|
||||
|
||||
for(var/i=toCopy.len,i>0,)
|
||||
objects.len++
|
||||
objects[objects.len] = toCopy[i--]
|
||||
|
||||
/datum/updateQueue/proc/Run()
|
||||
uq_dbg("Starting run...")
|
||||
|
||||
startWorker()
|
||||
while (istype(currentWorker) && !currentWorker.finished)
|
||||
sleep(2)
|
||||
checkWorker()
|
||||
|
||||
uq_dbg("UpdateQueue completed run.")
|
||||
|
||||
/datum/updateQueue/proc/checkWorker()
|
||||
if(istype(currentWorker))
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if(world.timeofday < currentWorker.lastStart)
|
||||
currentWorker.lastStart -= 864000
|
||||
|
||||
if(world.timeofday - currentWorker.lastStart > adjustedWorkerTimeout)
|
||||
// This worker is a bit slow, let's spawn a new one and kill the old one.
|
||||
uq_dbg("Current worker is lagging... starting a new one.")
|
||||
killWorker()
|
||||
startWorker()
|
||||
else // No worker!
|
||||
uq_dbg("update queue ended up without a worker... starting a new one...")
|
||||
startWorker()
|
||||
|
||||
/datum/updateQueue/proc/startWorker()
|
||||
// only run the worker if we have objects to work on
|
||||
if(objects.len)
|
||||
uq_dbg("Starting worker process.")
|
||||
|
||||
// No need to create a fresh worker if we already have one...
|
||||
if (istype(currentWorker))
|
||||
currentWorker.init(objects, procName, arguments)
|
||||
else
|
||||
currentWorker = new(objects, procName, arguments)
|
||||
currentWorker.start()
|
||||
else
|
||||
uq_dbg("Queue is empty. No worker was started.")
|
||||
currentWorker = null
|
||||
|
||||
/datum/updateQueue/proc/killWorker()
|
||||
// Kill the worker
|
||||
currentWorker.kill()
|
||||
currentWorker = null
|
||||
// After we kill a worker, yield so that if the worker's been tying up the cpu, other stuff can immediately resume
|
||||
sleep(-1)
|
||||
currentKillCount++
|
||||
totalKillCount++
|
||||
if (currentKillCount >= 3)
|
||||
uq_dbg("[currentKillCount] workers have been killed with a timeout of [adjustedWorkerTimeout]. Increasing worker timeout to compensate.")
|
||||
adjustedWorkerTimeout++
|
||||
currentKillCount = 0
|
||||
@@ -1,83 +0,0 @@
|
||||
datum/updateQueueWorker
|
||||
var/tmp/list/objects
|
||||
var/tmp/killed
|
||||
var/tmp/finished
|
||||
var/tmp/procName
|
||||
var/tmp/list/arguments
|
||||
var/tmp/lastStart
|
||||
var/tmp/cpuThreshold
|
||||
|
||||
datum/updateQueueWorker/New(var/list/objects, var/procName, var/list/arguments, var/cpuThreshold = 90)
|
||||
..()
|
||||
uq_dbg("updateQueueWorker created.")
|
||||
|
||||
init(objects, procName, arguments, cpuThreshold)
|
||||
|
||||
datum/updateQueueWorker/proc/init(var/list/objects, var/procName, var/list/arguments, var/cpuThreshold = 90)
|
||||
src.objects = objects
|
||||
src.procName = procName
|
||||
src.arguments = arguments
|
||||
src.cpuThreshold = cpuThreshold
|
||||
|
||||
killed = 0
|
||||
finished = 0
|
||||
|
||||
datum/updateQueueWorker/proc/doWork()
|
||||
// If there's nothing left to execute or we were killed, mark finished and return.
|
||||
if (!objects || !objects.len) return finished()
|
||||
|
||||
lastStart = world.timeofday // Absolute number of ticks since the world started up
|
||||
|
||||
var/datum/object = objects[objects.len] // Pull out the object
|
||||
objects.len-- // Remove the object from the list
|
||||
|
||||
if (istype(object) && !isturf(object) && !object.disposed && isnull(object.gcDestroyed)) // We only work with real objects
|
||||
call(object, procName)(arglist(arguments))
|
||||
|
||||
// If there's nothing left to execute
|
||||
// or we were killed while running the above code, mark finished and return.
|
||||
if (!objects || !objects.len) return finished()
|
||||
|
||||
if (world.cpu > cpuThreshold)
|
||||
// We don't want to force a tick into overtime!
|
||||
// If the tick is about to go overtime, spawn the next update to go
|
||||
// in the next tick.
|
||||
uq_dbg("tick went into overtime with world.cpu = [world.cpu], deferred next update to next tick [1+(world.time / world.tick_lag)]")
|
||||
|
||||
spawn(1)
|
||||
doWork()
|
||||
else
|
||||
spawn(0) // Execute anonymous function immediately as if we were in a while loop...
|
||||
doWork()
|
||||
|
||||
datum/updateQueueWorker/proc/finished()
|
||||
uq_dbg("updateQueueWorker finished.")
|
||||
/**
|
||||
* If the worker was killed while it was working on something, it
|
||||
* should delete itself when it finally finishes working on it.
|
||||
* Meanwhile, the updateQueue will have proceeded on with the rest of
|
||||
* the queue. This will also terminate the spawned function that was
|
||||
* created in the kill() proc.
|
||||
*/
|
||||
if(killed)
|
||||
del(src)
|
||||
|
||||
finished = 1
|
||||
|
||||
datum/updateQueueWorker/proc/kill()
|
||||
uq_dbg("updateQueueWorker killed.")
|
||||
killed = 1
|
||||
objects = null
|
||||
|
||||
/**
|
||||
* If the worker is not done in 30 seconds after it's killed,
|
||||
* we'll forcibly delete it, causing the anonymous function it was
|
||||
* running to be terminated. Hasta la vista, baby.
|
||||
*/
|
||||
spawn(300)
|
||||
del(src)
|
||||
|
||||
datum/updateQueueWorker/proc/start()
|
||||
uq_dbg("updateQueueWorker started.")
|
||||
spawn(0)
|
||||
doWork()
|
||||
@@ -1,94 +0,0 @@
|
||||
/datum/processSchedulerView
|
||||
|
||||
/datum/processSchedulerView/Topic(href, href_list)
|
||||
if (!href_list["action"])
|
||||
return
|
||||
|
||||
switch (href_list["action"])
|
||||
if ("kill")
|
||||
var/toKill = href_list["name"]
|
||||
processScheduler.killProcess(toKill)
|
||||
refreshProcessTable()
|
||||
if ("enable")
|
||||
var/toEnable = href_list["name"]
|
||||
processScheduler.enableProcess(toEnable)
|
||||
refreshProcessTable()
|
||||
if ("disable")
|
||||
var/toDisable = href_list["name"]
|
||||
processScheduler.disableProcess(toDisable)
|
||||
refreshProcessTable()
|
||||
if ("refresh")
|
||||
refreshProcessTable()
|
||||
|
||||
/datum/processSchedulerView/proc/refreshProcessTable()
|
||||
windowCall("handleRefresh", getProcessTable())
|
||||
|
||||
/datum/processSchedulerView/proc/windowCall(var/function, var/data = null)
|
||||
usr << output(data, "processSchedulerContext.browser:[function]")
|
||||
|
||||
/datum/processSchedulerView/proc/getProcessTable()
|
||||
var/text = "<table class=\"table table-striped\"><thead><tr><td>Name</td><td>Avg(s)</td><td>Last(s)</td><td>Highest(s)</td><td>Tickcount</td><td>Tickrate</td><td>State</td><td>Action</td></tr></thead><tbody>"
|
||||
// and the context of each
|
||||
for (var/list/data in processScheduler.getStatusData())
|
||||
text += "<tr>"
|
||||
text += "<td>[data["name"]]</td>"
|
||||
text += "<td>[num2text(data["averageRunTime"]/10,3)]</td>"
|
||||
text += "<td>[num2text(data["lastRunTime"]/10,3)]</td>"
|
||||
text += "<td>[num2text(data["highestRunTime"]/10,3)]</td>"
|
||||
text += "<td>[num2text(data["ticks"],4)]</td>"
|
||||
text += "<td>[data["schedule"]]</td>"
|
||||
text += "<td>[data["status"]]</td>"
|
||||
text += "<td><button class=\"btn kill-btn\" data-process-name=\"[data["name"]]\" id=\"kill-[data["name"]]\">Kill</button>"
|
||||
if (data["disabled"])
|
||||
text += "<button class=\"btn enable-btn\" data-process-name=\"[data["name"]]\" id=\"enable-[data["name"]]\">Enable</button>"
|
||||
else
|
||||
text += "<button class=\"btn disable-btn\" data-process-name=\"[data["name"]]\" id=\"disable-[data["name"]]\">Disable</button>"
|
||||
text += "</td>"
|
||||
text += "</tr>"
|
||||
|
||||
text += "</tbody></table>"
|
||||
return text
|
||||
|
||||
/**
|
||||
* getContext
|
||||
* Outputs an interface showing stats for all processes.
|
||||
*/
|
||||
/datum/processSchedulerView/proc/getContext()
|
||||
bootstrap_browse()
|
||||
usr << browse('processScheduler.js', "file=processScheduler.js;display=0")
|
||||
|
||||
var/text = {"<html><head>
|
||||
<title>Process Scheduler Detail</title>
|
||||
<script type="text/javascript">var ref = '\ref[src]';</script>
|
||||
[bootstrap_includes()]
|
||||
<script type="text/javascript" src="processScheduler.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Process Scheduler</h2>
|
||||
<div class="btn-group">
|
||||
<button id="btn-refresh" class="btn">Refresh</button>
|
||||
</div>
|
||||
|
||||
<h3>The process scheduler controls [processScheduler.getProcessCount()] loops.<h3>"}
|
||||
|
||||
text += "<div id=\"processTable\">"
|
||||
text += getProcessTable()
|
||||
text += "</div></body></html>"
|
||||
|
||||
usr << browse(text, "window=processSchedulerContext;size=800x600")
|
||||
|
||||
/datum/processSchedulerView/proc/bootstrap_browse()
|
||||
usr << browse('bower_components/jquery/dist/jquery.min.js', "file=jquery.min.js;display=0")
|
||||
usr << browse('bower_components/bootstrap2.3.2/bootstrap/js/bootstrap.min.js', "file=bootstrap.min.js;display=0")
|
||||
usr << browse('bower_components/bootstrap2.3.2/bootstrap/css/bootstrap.min.css', "file=bootstrap.min.css;display=0")
|
||||
usr << browse('bower_components/bootstrap2.3.2/bootstrap/img/glyphicons-halflings-white.png', "file=glyphicons-halflings-white.png;display=0")
|
||||
usr << browse('bower_components/bootstrap2.3.2/bootstrap/img/glyphicons-halflings.png', "file=glyphicons-halflings.png;display=0")
|
||||
usr << browse('bower_components/json2/json2.js', "file=json2.js;display=0")
|
||||
|
||||
/datum/processSchedulerView/proc/bootstrap_includes()
|
||||
return {"
|
||||
<link rel="stylesheet" href="bootstrap.min.css" />
|
||||
<script type="text/javascript" src="json2.js"></script>
|
||||
<script type="text/javascript" src="jquery.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap.js"></script>
|
||||
"}
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* testDyingUpdateQueueProcess
|
||||
* This process is an example of a process using an updateQueue.
|
||||
* The datums updated by this process behave badly and block the update loop
|
||||
* by sleeping. If you #define UPDATE_QUEUE_DEBUG, you will see the updateQueue
|
||||
* killing off its worker processes and spawning new ones to work around slow
|
||||
* updates. This means that if you have a code path that sleeps for a long time
|
||||
* in mob.Life once in a blue moon, the mob update loop will not hang.
|
||||
*/
|
||||
/datum/slowTestDatum/proc/wackyUpdateProcessName()
|
||||
sleep(rand(0,20)) // Randomly REALLY slow :|
|
||||
|
||||
/datum/controller/process/testDyingUpdateQueueProcess
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
var/tmp/list/testDatums = list()
|
||||
|
||||
/datum/controller/process/testDyingUpdateQueueProcess/setup()
|
||||
name = "Dying UpdateQueue Process"
|
||||
schedule_interval = 30 // every 3 seconds
|
||||
updateQueueInstance = new
|
||||
for(var/i = 1, i < 30, i++)
|
||||
testDatums.Add(new /datum/slowTestDatum)
|
||||
|
||||
/datum/controller/process/testDyingUpdateQueueProcess/doWork()
|
||||
updateQueueInstance.init(testDatums, "wackyUpdateProcessName")
|
||||
updateQueueInstance.Run()
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
These are simple defaults for your project.
|
||||
*/
|
||||
#define DEBUG
|
||||
|
||||
var/global/datum/processSchedulerView/processSchedulerView
|
||||
|
||||
world
|
||||
loop_checks = 0
|
||||
New()
|
||||
..()
|
||||
processScheduler = new
|
||||
processSchedulerView = new
|
||||
|
||||
mob
|
||||
step_size = 8
|
||||
|
||||
New()
|
||||
..()
|
||||
|
||||
|
||||
verb
|
||||
startProcessScheduler()
|
||||
set name = "Start Process Scheduler"
|
||||
processScheduler.setup()
|
||||
processScheduler.start()
|
||||
|
||||
getProcessSchedulerContext()
|
||||
set name = "Get Process Scheduler Status Panel"
|
||||
processSchedulerView.getContext()
|
||||
|
||||
runUpdateQueueTests()
|
||||
set name = "Run Update Queue Testsuite"
|
||||
var/datum/updateQueueTests/t = new
|
||||
t.runTests()
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* testHungProcess
|
||||
* This process is an example of a simple update loop process that hangs.
|
||||
*/
|
||||
|
||||
/datum/controller/process/testHungProcess/setup()
|
||||
name = "Hung Process"
|
||||
schedule_interval = 30 // every 3 seconds
|
||||
|
||||
/datum/controller/process/testHungProcess/doWork()
|
||||
sleep(1000) // FUCK
|
||||
// scheck is also responsible for handling hung processes. If a process
|
||||
// hangs, and later resumes, but has already been killed by the scheduler,
|
||||
// scheck will force the process to bail out.
|
||||
scheck()
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* testNiceProcess
|
||||
* This process is an example of a simple update loop process that is
|
||||
* relatively fast.
|
||||
*/
|
||||
|
||||
/datum/controller/process/testNiceProcess/setup()
|
||||
name = "Nice Process"
|
||||
schedule_interval = 10 // every second
|
||||
|
||||
/datum/controller/process/testNiceProcess/doWork()
|
||||
sleep(rand(1,5)) // Just to pretend we're doing something
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* testSlowProcess
|
||||
* This process is an example of a simple update loop process that is slow.
|
||||
* The update loop here sleeps inside to provide an example, but if you had
|
||||
* a computationally intensive loop process that is simply slow, you can use
|
||||
* scheck() inside the loop to force it to yield periodically according to
|
||||
* the sleep_interval var. By default, scheck will cause a loop to sleep every
|
||||
* 2 ticks.
|
||||
*/
|
||||
|
||||
/datum/controller/process/testSlowProcess/setup()
|
||||
name = "Slow Process"
|
||||
schedule_interval = 30 // every 3 seconds
|
||||
|
||||
/datum/controller/process/testSlowProcess/doWork()
|
||||
// set background = 1 will cause loop constructs to sleep periodically,
|
||||
// whenever the BYOND scheduler deems it productive to do so.
|
||||
// This behavior is not always sufficient, nor is it always consistent.
|
||||
// Rather than leaving it up to the BYOND scheduler, we can control it
|
||||
// ourselves and leave nothing to the black box.
|
||||
set background = 1
|
||||
|
||||
for(var/i=1,i<30,i++)
|
||||
// Just to pretend we're doing something here
|
||||
sleep(rand(3, 5))
|
||||
|
||||
// Forces this loop to yield(sleep) periodically.
|
||||
scheck()
|
||||
@@ -1,209 +0,0 @@
|
||||
var/global/list/updateQueueTestCount = list()
|
||||
|
||||
/datum/updateQueueTests
|
||||
var/start
|
||||
proc
|
||||
runTests()
|
||||
world << "<b>Running 9 tests...</b>"
|
||||
testUpdateQueuePerformance()
|
||||
sleep(1)
|
||||
testInplace()
|
||||
sleep(1)
|
||||
testInplaceUpdateQueuePerformance()
|
||||
sleep(1)
|
||||
testUpdateQueueReinit()
|
||||
sleep(1)
|
||||
testCrashingQueue()
|
||||
sleep(1)
|
||||
testEmptyQueue()
|
||||
sleep(1)
|
||||
testManySlowItemsInQueue()
|
||||
sleep(1)
|
||||
testVariableWorkerTimeout()
|
||||
sleep(1)
|
||||
testReallySlowItemInQueue()
|
||||
sleep(1)
|
||||
world << "<b>Finished!</b>"
|
||||
|
||||
beginTiming()
|
||||
start = world.time
|
||||
|
||||
endTiming(text)
|
||||
var/time = (world.time - start) / world.tick_lag
|
||||
world << {"<b><font color="blue">Performance - [text] - <font color="green">[time]</font> ticks</font></b>"}
|
||||
|
||||
getCount()
|
||||
return updateQueueTestCount[updateQueueTestCount.len]
|
||||
|
||||
incrementTestCount()
|
||||
updateQueueTestCount.len++
|
||||
updateQueueTestCount[updateQueueTestCount.len] = 0
|
||||
|
||||
assertCountEquals(count, text)
|
||||
assertThat(getCount() == count, text)
|
||||
|
||||
assertCountLessThan(count, text)
|
||||
assertThat(getCount() < count, text)
|
||||
|
||||
assertCountGreaterThan(count, text)
|
||||
assertThat(getCount() > count, text)
|
||||
|
||||
assertThat(condition, text)
|
||||
if (condition)
|
||||
world << {"<font color="green"><b>PASS</b></font>: [text]"}
|
||||
else
|
||||
world << {"<b><font color="red">FAIL</font>: [text]</b>"}
|
||||
|
||||
testUpdateQueuePerformance()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=100000,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
|
||||
beginTiming()
|
||||
uq.Run()
|
||||
endTiming("updating 100000 simple objects")
|
||||
|
||||
assertCountEquals(100000, "test that update queue updates all objects expected")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testUpdateQueueReinit()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=100,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
objs = new
|
||||
|
||||
for(var/i=1,i<=100,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
uq.init(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(200, "test that update queue reinitializes properly and updates all objects as expected.")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testInplace()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=100,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
var/datum/updateQueue/uq = new(objects = objs, inplace = 1)
|
||||
uq.Run()
|
||||
assertThat(objs.len == 0, "test that update queue inplace option really works inplace")
|
||||
assertCountEquals(100, "test that inplace update queue updates the right number of objects")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testInplaceUpdateQueuePerformance()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=100000,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
|
||||
beginTiming()
|
||||
uq.Run()
|
||||
endTiming("updating 100000 simple objects in place")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testCrashingQueue()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=10,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
objs.Add(new /datum/uqTestDatum/crasher(updateQueueTestCount.len))
|
||||
for(var/i=1,i<=10,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(20, "test that update queue handles crashed update procs OK")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testEmptyQueue()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(0, "test that update queue doesn't barf on empty lists")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testManySlowItemsInQueue()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=30,i++)
|
||||
objs.Add(new /datum/uqTestDatum/slow(updateQueueTestCount.len))
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(30, "test that update queue slows down execution if too many objects are slow to update")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testVariableWorkerTimeout()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=20,i++)
|
||||
objs.Add(new /datum/uqTestDatum/slow(updateQueueTestCount.len))
|
||||
var/datum/updateQueue/uq = new(objs, workerTimeout=6)
|
||||
uq.Run()
|
||||
assertCountEquals(20, "test that variable worker timeout works properly")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testReallySlowItemInQueue()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=10,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
objs.Add(new /datum/uqTestDatum/reallySlow(updateQueueTestCount.len))
|
||||
for(var/i=1,i<=10,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(20, "test that update queue skips objects that are too slow to update")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
|
||||
|
||||
datum/uqTestDatum
|
||||
var/testNum
|
||||
New(testNum)
|
||||
..()
|
||||
src.testNum = testNum
|
||||
proc/update()
|
||||
updateQueueTestCount[testNum]++
|
||||
proc/lag(cycles)
|
||||
set background = 1
|
||||
for(var/i=0,i<cycles,)
|
||||
i++
|
||||
datum/uqTestDatum/fast
|
||||
|
||||
datum/uqTestDatum/slow
|
||||
update()
|
||||
set background = 1
|
||||
var/start = world.timeofday
|
||||
while(world.timeofday - start < 5) // lag 4 deciseconds
|
||||
..()
|
||||
|
||||
datum/uqTestDatum/reallySlow
|
||||
update()
|
||||
set background = 1
|
||||
var/start = world.timeofday
|
||||
while(world.timeofday - start < 300) // lag 30 seconds
|
||||
..()
|
||||
|
||||
datum/uqTestDatum/crasher
|
||||
update()
|
||||
CRASH("I crashed! (I am supposed to crash XD)")
|
||||
..() // This should do nothing lol
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* testUpdateQueueProcess
|
||||
* This process is an example of a process using an updateQueue.
|
||||
* The datums updated by this process behave nicely and do not block.
|
||||
*/
|
||||
|
||||
/datum/fastTestDatum/proc/wackyUpdateProcessName()
|
||||
sleep(prob(10)) // Pretty quick, usually instant
|
||||
|
||||
/datum/controller/process/testUpdateQueueProcess
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
var/tmp/list/testDatums = list()
|
||||
|
||||
/datum/controller/process/testUpdateQueueProcess/setup()
|
||||
name = "UpdateQueue Process"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
updateQueueInstance = new
|
||||
for(var/i = 1, i < 30, i++)
|
||||
testDatums.Add(new /datum/fastTestDatum)
|
||||
|
||||
/datum/controller/process/testUpdateQueueProcess/doWork()
|
||||
updateQueueInstance.init(testDatums, "wackyUpdateProcessName")
|
||||
updateQueueInstance.Run()
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* testBadZombieProcess
|
||||
* This process is an example of a simple update loop process that hangs.
|
||||
*/
|
||||
|
||||
/datum/controller/process/testZombieProcess/setup()
|
||||
name = "Zombie Process"
|
||||
schedule_interval = 30 // every 3 seconds
|
||||
|
||||
/datum/controller/process/testZombieProcess/doWork()
|
||||
for (var/i = 0, i < 1000, i++)
|
||||
sleep(1)
|
||||
scheck()
|
||||
@@ -1,6 +1,7 @@
|
||||
/datum/controller/process/air/setup()
|
||||
name = "air"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
start_delay = 4
|
||||
|
||||
if(!air_master)
|
||||
air_master = new
|
||||
|
||||
@@ -1,10 +1,40 @@
|
||||
// We manually initialize the alarm handlers instead of looping over all existing types
|
||||
// to make it possible to write: camera.triggerAlarm() rather than alarm_manager.managers[datum/alarm_handler/camera].triggerAlarm() or a variant thereof.
|
||||
/var/global/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
|
||||
/var/global/datum/alarm_handler/camera/camera_alarm = new()
|
||||
/var/global/datum/alarm_handler/fire/fire_alarm = new()
|
||||
/var/global/datum/alarm_handler/motion/motion_alarm = new()
|
||||
/var/global/datum/alarm_handler/power/power_alarm = new()
|
||||
|
||||
// Alarm Manager, the manager for alarms.
|
||||
var/datum/controller/process/alarm/alarm_manager
|
||||
|
||||
/datum/controller/process/alarm
|
||||
var/list/datum/alarm/all_handlers
|
||||
|
||||
/datum/controller/process/alarm/setup()
|
||||
name = "alarm"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
all_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, motion_alarm, power_alarm)
|
||||
alarm_manager = src
|
||||
|
||||
/datum/controller/process/alarm/doWork()
|
||||
alarm_manager.fire()
|
||||
for(var/datum/alarm_handler/AH in all_handlers)
|
||||
AH.process()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/alarm/getStatName()
|
||||
var/list/alarms = alarm_manager.active_alarms()
|
||||
return ..()+"([alarms.len])"
|
||||
/datum/controller/process/alarm/proc/active_alarms()
|
||||
var/list/all_alarms = new
|
||||
for(var/datum/alarm_handler/AH in all_handlers)
|
||||
var/list/alarms = AH.alarms
|
||||
all_alarms += alarms
|
||||
|
||||
return all_alarms
|
||||
|
||||
/datum/controller/process/alarm/proc/number_of_active_alarms()
|
||||
var/list/alarms = active_alarms()
|
||||
return alarms.len
|
||||
|
||||
/datum/controller/process/alarm/statProcess()
|
||||
..()
|
||||
stat(null, "[number_of_active_alarms()] alarm\s")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
var/datum/controller/process/chemistry/chemistryProcess
|
||||
|
||||
/datum/controller/process/chemistry
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
var/list/active_holders
|
||||
var/list/chemical_reactions
|
||||
var/list/chemical_reagents
|
||||
@@ -9,23 +8,23 @@ var/datum/controller/process/chemistry/chemistryProcess
|
||||
/datum/controller/process/chemistry/setup()
|
||||
name = "chemistry"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
updateQueueInstance = new
|
||||
chemistryProcess = src
|
||||
active_holders = list()
|
||||
chemical_reactions = chemical_reactions_list
|
||||
chemical_reagents = chemical_reagents_list
|
||||
|
||||
/datum/controller/process/chemistry/getStatName()
|
||||
return ..()+"([active_holders.len])"
|
||||
/datum/controller/process/chemistry/statProcess()
|
||||
..()
|
||||
stat(null, "[active_holders.len] reagent holder\s")
|
||||
|
||||
/datum/controller/process/chemistry/doWork()
|
||||
for(var/datum/reagents/holder in active_holders)
|
||||
if(!holder.process_reactions())
|
||||
active_holders -= holder
|
||||
scheck()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/chemistry/proc/mark_for_update(var/datum/reagents/holder)
|
||||
if(holder in active_holders)
|
||||
if(holder in active_holders)
|
||||
return
|
||||
|
||||
//Process once, right away. If we still need to continue then add to the active_holders list and continue later
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
/datum/controller/process/disease/setup()
|
||||
name = "disease"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
updateQueueInstance = new
|
||||
|
||||
/datum/controller/process/disease/doWork()
|
||||
updateQueueInstance.init(active_diseases, "process")
|
||||
updateQueueInstance.Run()
|
||||
for(var/disease in active_diseases)
|
||||
var/datum/disease/D = disease
|
||||
D.process()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/disease/getStatName()
|
||||
return ..()+"([active_diseases.len])"
|
||||
/datum/controller/process/disease/statProcess()
|
||||
..()
|
||||
stat(null, "[active_diseases.len] disease\s")
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
// The time a datum was destroyed by the GC, or null if it hasn't been
|
||||
/datum/var/gcDestroyed
|
||||
|
||||
#define GC_COLLECTIONS_PER_RUN 150
|
||||
#define GC_COLLECTION_TIMEOUT (30 SECONDS)
|
||||
#define GC_FORCE_DEL_PER_RUN 30
|
||||
|
||||
var/datum/controller/process/garbage_collector/garbage_collector
|
||||
var/list/delayed_garbage = list()
|
||||
|
||||
/datum/controller/process/garbage_collector
|
||||
var/garbage_collect = 1 // Whether or not to actually do work
|
||||
var/collection_timeout = 300 //deciseconds to wait to let running procs finish before we just say fuck it and force del() the object
|
||||
var/max_checks_multiplier = 5 //multiplier (per-decisecond) for calculating max number of tests per tick. These tests check if our GC'd objects are actually GC'd
|
||||
var/max_forcedel_multiplier = 1 //multiplier (per-decisecond) for calculating max number of force del() calls per tick.
|
||||
|
||||
var/dels = 0 // number of del()'s we've done this tick
|
||||
var/total_dels = 0 // number of total del()'s
|
||||
var/tick_dels = 0 // number of del()'s we've done this tick
|
||||
var/soft_dels = 0
|
||||
var/hard_dels = 0 // number of hard dels in total
|
||||
var/list/destroyed = 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()
|
||||
@@ -18,7 +23,8 @@ var/list/delayed_garbage = list()
|
||||
|
||||
/datum/controller/process/garbage_collector/setup()
|
||||
name = "garbage"
|
||||
schedule_interval = 2 SECONDS
|
||||
schedule_interval = 10 SECONDS
|
||||
start_delay = 3
|
||||
|
||||
if(!garbage_collector)
|
||||
garbage_collector = src
|
||||
@@ -36,10 +42,10 @@ world/loop_checks = 0
|
||||
if(!garbage_collect)
|
||||
return
|
||||
|
||||
dels = 0
|
||||
var/time_to_kill = world.time - collection_timeout // Anything qdel() but not GC'd BEFORE this time needs to be manually del()
|
||||
var/checkRemain = max_checks_multiplier * schedule_interval
|
||||
var/maxDels = max_forcedel_multiplier * schedule_interval
|
||||
tick_dels = 0
|
||||
var/time_to_kill = world.time - GC_COLLECTION_TIMEOUT
|
||||
var/checkRemain = GC_COLLECTIONS_PER_RUN
|
||||
var/remaining_force_dels = GC_FORCE_DEL_PER_RUN
|
||||
|
||||
#ifdef GC_FINDREF
|
||||
var/list/searching = list()
|
||||
@@ -67,7 +73,7 @@ world/loop_checks = 0
|
||||
#endif
|
||||
|
||||
while(destroyed.len && --checkRemain >= 0)
|
||||
if(dels >= maxDels)
|
||||
if(remaining_force_dels <= 0)
|
||||
#ifdef GC_DEBUG
|
||||
testing("GC: Reached max force dels per tick [dels] vs [maxDels]")
|
||||
#endif
|
||||
@@ -88,13 +94,22 @@ world/loop_checks = 0
|
||||
testing("GC: -- \ref[A] | [A.type] was unable to be GC'd and was deleted --")
|
||||
logging["[A.type]"]++
|
||||
del(A)
|
||||
++dels
|
||||
++hard_dels
|
||||
#ifdef GC_DEBUG
|
||||
|
||||
hard_dels++
|
||||
remaining_force_dels--
|
||||
else
|
||||
#ifdef GC_DEBUG
|
||||
testing("GC: [refID] properly GC'd at [world.time] with timeout [GCd_at_time]")
|
||||
#endif
|
||||
#endif
|
||||
soft_dels++
|
||||
tick_dels++
|
||||
total_dels++
|
||||
destroyed.Cut(1, 2)
|
||||
SCHECK
|
||||
|
||||
#undef GC_FORCE_DEL_PER_TICK
|
||||
#undef GC_COLLECTION_TIMEOUT
|
||||
#undef GC_COLLECTIONS_PER_TICK
|
||||
|
||||
#ifdef GC_FINDREF
|
||||
/datum/controller/process/garbage_collector/proc/LookForRefs(var/datum/D, var/list/targ)
|
||||
@@ -132,8 +147,11 @@ world/loop_checks = 0
|
||||
destroyed -= "\ref[A]" // Removing any previous references that were GC'd so that the current object will be at the end of the list.
|
||||
destroyed["\ref[A]"] = world.time
|
||||
|
||||
/datum/controller/process/garbage_collector/getStatName()
|
||||
return ..()+"([garbage_collector.destroyed.len]/[garbage_collector.dels]/[garbage_collector.hard_dels])"
|
||||
/datum/controller/process/garbage_collector/statProcess()
|
||||
..()
|
||||
stat(null, "[garbage_collect ? "On" : "Off"], [destroyed.len] queued")
|
||||
stat(null, "Dels: [total_dels], [soft_dels] soft, [hard_dels] hard, [tick_dels] last run")
|
||||
|
||||
|
||||
// Tests if an atom has been deleted.
|
||||
/proc/deleted(atom/A)
|
||||
@@ -149,7 +167,7 @@ world/loop_checks = 0
|
||||
crash_with("qdel() passed object of type [A.type]. qdel() can only handle /datum types.")
|
||||
del(A)
|
||||
if(garbage_collector)
|
||||
garbage_collector.dels++
|
||||
garbage_collector.total_dels++
|
||||
garbage_collector.hard_dels++
|
||||
else if(isnull(A.gcDestroyed))
|
||||
// Let our friend know they're about to get collected
|
||||
|
||||
@@ -10,4 +10,4 @@
|
||||
log_access("AFK: [key_name(C)]")
|
||||
C << "<SPAN CLASS='warning'>You have been inactive for more than [config.kick_inactive] minute\s and have been disconnected.</SPAN>"
|
||||
del(C) // Don't qdel, cannot override finalize_qdel behaviour for clients.
|
||||
scheck()
|
||||
SCHECK
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/datum/controller/process/lighting/setup()
|
||||
name = "lighting"
|
||||
start_delay = 1
|
||||
schedule_interval = 5 // every .5 second
|
||||
lighting_controller.initializeLighting()
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/datum/controller/process/machinery/setup()
|
||||
name = "machinery"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
start_delay = 12
|
||||
|
||||
/datum/controller/process/machinery/doWork()
|
||||
internal_sort()
|
||||
@@ -19,10 +20,6 @@
|
||||
/datum/controller/process/machinery/proc/internal_process_machinery()
|
||||
for(var/obj/machinery/M in machines)
|
||||
if(M && !M.gcDestroyed)
|
||||
#ifdef PROFILE_MACHINES
|
||||
var/time_start = world.timeofday
|
||||
#endif
|
||||
|
||||
if(M.process() == PROCESS_KILL)
|
||||
//M.inMachineList = 0 We don't use this debugging function
|
||||
machines.Remove(M)
|
||||
@@ -31,22 +28,13 @@
|
||||
if(M && M.use_power)
|
||||
M.auto_use_power()
|
||||
|
||||
#ifdef PROFILE_MACHINES
|
||||
var/time_end = world.timeofday
|
||||
|
||||
if(!(M.type in machine_profiling))
|
||||
machine_profiling[M.type] = 0
|
||||
|
||||
machine_profiling[M.type] += (time_end - time_start)
|
||||
#endif
|
||||
|
||||
scheck()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/machinery/proc/internal_process_power()
|
||||
for(var/datum/powernet/powerNetwork in powernets)
|
||||
if(istype(powerNetwork) && !powerNetwork.disposed)
|
||||
if(istype(powerNetwork) && isnull(powerNetwork.gcDestroyed))
|
||||
powerNetwork.reset()
|
||||
scheck()
|
||||
SCHECK
|
||||
continue
|
||||
|
||||
powernets.Remove(powerNetwork)
|
||||
@@ -56,16 +44,20 @@
|
||||
for(var/obj/item/I in processing_power_items)
|
||||
if(!I.pwr_drain()) // 0 = Process Kill, remove from processing list.
|
||||
processing_power_items.Remove(I)
|
||||
scheck()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/machinery/proc/internal_process_pipenets()
|
||||
for(var/datum/pipe_network/pipeNetwork in pipe_networks)
|
||||
if(istype(pipeNetwork) && !pipeNetwork.disposed)
|
||||
if(istype(pipeNetwork) && isnull(pipeNetwork.gcDestroyed))
|
||||
pipeNetwork.process()
|
||||
scheck()
|
||||
SCHECK
|
||||
continue
|
||||
|
||||
pipe_networks.Remove(pipeNetwork)
|
||||
|
||||
/datum/controller/process/machinery/getStatName()
|
||||
return ..()+"(MCH:[machines.len] PWR:[powernets.len] PIP:[pipe_networks.len])"
|
||||
/datum/controller/process/machinery/statProcess()
|
||||
..()
|
||||
stat(null, "[machines.len] machines")
|
||||
stat(null, "[powernets.len] powernets")
|
||||
stat(null, "[pipe_networks.len] pipenets")
|
||||
stat(null, "[processing_power_items.len] power item\s")
|
||||
|
||||
@@ -4,20 +4,26 @@
|
||||
/datum/controller/process/mob/setup()
|
||||
name = "mob"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
updateQueueInstance = new
|
||||
start_delay = 16
|
||||
|
||||
/datum/controller/process/mob/started()
|
||||
..()
|
||||
if(!updateQueueInstance)
|
||||
if(!mob_list)
|
||||
mob_list = list()
|
||||
else if(mob_list.len)
|
||||
updateQueueInstance = new
|
||||
if(!mob_list)
|
||||
mob_list = list()
|
||||
|
||||
/datum/controller/process/mob/doWork()
|
||||
if(updateQueueInstance)
|
||||
updateQueueInstance.init(mob_list, "Life")
|
||||
updateQueueInstance.Run()
|
||||
for(last_object in mob_list)
|
||||
var/mob/M = last_object
|
||||
if(isnull(M.gcDestroyed))
|
||||
try
|
||||
M.Life()
|
||||
catch(var/exception/e)
|
||||
catchException(e, M)
|
||||
SCHECK
|
||||
else
|
||||
catchBadType(M)
|
||||
mob_list -= M
|
||||
|
||||
/datum/controller/process/mob/getStatName()
|
||||
return ..()+"([mob_list.len])"
|
||||
/datum/controller/process/mob/statProcess()
|
||||
..()
|
||||
stat(null, "[mob_list.len] mobs")
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
/datum/controller/process/nanoui
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
|
||||
/datum/controller/process/nanoui/setup()
|
||||
name = "nanoui"
|
||||
schedule_interval = 10 // every 1 second
|
||||
updateQueueInstance = new
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
|
||||
/datum/controller/process/nanoui/statProcess()
|
||||
..()
|
||||
stat(null, "[nanomanager.processing_uis.len] UIs")
|
||||
|
||||
/datum/controller/process/nanoui/doWork()
|
||||
updateQueueInstance.init(nanomanager.processing_uis, "process")
|
||||
updateQueueInstance.Run()
|
||||
|
||||
/datum/controller/process/nanoui/getStatName()
|
||||
return ..()+"([nanomanager.processing_uis.len])"
|
||||
for(last_object in nanomanager.processing_uis)
|
||||
var/datum/nanoui/NUI = last_object
|
||||
if(istype(NUI) && isnull(NUI.gcDestroyed))
|
||||
try
|
||||
NUI.process()
|
||||
catch(var/exception/e)
|
||||
catchException(e, NUI)
|
||||
else
|
||||
catchBadType(NUI)
|
||||
nanomanager.processing_uis -= NUI
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
var/global/list/object_profiling = list()
|
||||
/datum/controller/process/obj
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
|
||||
/datum/controller/process/obj/setup()
|
||||
name = "obj"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
updateQueueInstance = new
|
||||
start_delay = 8
|
||||
|
||||
/datum/controller/process/obj/started()
|
||||
..()
|
||||
if(!updateQueueInstance)
|
||||
if(!processing_objects)
|
||||
processing_objects = list()
|
||||
else if(processing_objects.len)
|
||||
updateQueueInstance = new
|
||||
if(!processing_objects)
|
||||
processing_objects = list()
|
||||
|
||||
/datum/controller/process/obj/doWork()
|
||||
if(updateQueueInstance)
|
||||
updateQueueInstance.init(processing_objects, "process")
|
||||
updateQueueInstance.Run()
|
||||
for(last_object in processing_objects)
|
||||
var/datum/O = last_object
|
||||
if(isnull(O.gcDestroyed))
|
||||
try
|
||||
O:process()
|
||||
catch(var/exception/e)
|
||||
catchException(e, O)
|
||||
SCHECK
|
||||
else
|
||||
catchBadType(O)
|
||||
processing_objects -= O
|
||||
|
||||
/datum/controller/process/obj/getStatName()
|
||||
return ..()+"([processing_objects.len])"
|
||||
/datum/controller/process/obj/statProcess()
|
||||
..()
|
||||
stat(null, "[processing_objects.len] objects")
|
||||
|
||||
@@ -8,7 +8,8 @@ var/global/list/turf/processing_turfs = list()
|
||||
for(var/turf/T in processing_turfs)
|
||||
if(T.process() == PROCESS_KILL)
|
||||
processing_turfs.Remove(T)
|
||||
scheck()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/turf/getStatName()
|
||||
return ..()+"([processing_turfs.len])"
|
||||
/datum/controller/process/turf/statProcess()
|
||||
..()
|
||||
stat(null, "[processing_turfs.len] turf\s")
|
||||
|
||||
71
code/controllers/Processes/wireless.dm
Normal file
71
code/controllers/Processes/wireless.dm
Normal file
@@ -0,0 +1,71 @@
|
||||
//-------------------------------
|
||||
/*
|
||||
Wireless controller
|
||||
|
||||
Used for connecting devices to each other (i.e. machinery, doors, emitters, etc.)
|
||||
Unlike the radio controller, the wireless controller does not pass communications between devices. Once the devices
|
||||
have been connected they call each others procs directly, they do not use the wireless controller to communicate.
|
||||
|
||||
See code\modules\wireless\interfaces.dm for details of how to connect devices.
|
||||
*/
|
||||
//-------------------------------
|
||||
|
||||
var/datum/controller/process/wireless/wirelessProcess
|
||||
|
||||
/datum/controller/process/wireless
|
||||
var/list/receiver_list
|
||||
var/list/pending_connections
|
||||
var/list/retry_connections
|
||||
var/list/failed_connections
|
||||
|
||||
/datum/controller/process/wireless/setup()
|
||||
name = "wireless"
|
||||
schedule_interval = 50
|
||||
pending_connections = new()
|
||||
retry_connections = new()
|
||||
failed_connections = new()
|
||||
receiver_list = new()
|
||||
wirelessProcess = src
|
||||
|
||||
/datum/controller/process/wireless/proc/add_device(var/datum/wifi/receiver/R)
|
||||
if(receiver_list)
|
||||
receiver_list |= R
|
||||
else
|
||||
receiver_list = new()
|
||||
receiver_list |= R
|
||||
|
||||
/datum/controller/process/wireless/proc/remove_device(var/datum/wifi/receiver/R)
|
||||
if(receiver_list)
|
||||
receiver_list -= R
|
||||
|
||||
/datum/controller/process/wireless/proc/add_request(var/datum/connection_request/C)
|
||||
if(pending_connections)
|
||||
pending_connections += C
|
||||
else
|
||||
pending_connections = new()
|
||||
pending_connections += C
|
||||
|
||||
/datum/controller/process/wireless/doWork()
|
||||
//process any connection requests waiting to be retried
|
||||
if(retry_connections.len > 0)
|
||||
//any that fail are moved into the failed connections list
|
||||
process_queue(retry_connections, failed_connections)
|
||||
|
||||
//process any pending connection requests
|
||||
if(pending_connections.len > 0)
|
||||
//any that fail are moved to the retry queue
|
||||
process_queue(pending_connections, retry_connections)
|
||||
|
||||
/datum/controller/process/wireless/proc/process_queue(var/list/process_conections, var/list/unsuccesful_connections)
|
||||
for(var/datum/connection_request/C in process_conections)
|
||||
var/target_found = 0
|
||||
for(var/datum/wifi/receiver/R in receiver_list)
|
||||
if(R.id == C.id)
|
||||
var/datum/wifi/sender/S = C.source
|
||||
S.connect_device(R)
|
||||
R.connect_device(S)
|
||||
target_found = 1
|
||||
process_conections -= C
|
||||
if(!target_found)
|
||||
unsuccesful_connections += C
|
||||
SCHECK
|
||||
@@ -21,6 +21,7 @@ var/list/gamemode_cache = list()
|
||||
var/log_pda = 0 // log pda messages
|
||||
var/log_hrefs = 0 // logs all links clicked in-game. Could be used for debugging and tracking down exploits
|
||||
var/log_runtime = 0 // logs world.log to a file
|
||||
var/log_world_output = 0 // log world.log << messages
|
||||
var/sql_enabled = 1 // for sql switching
|
||||
var/allow_admin_ooccolor = 0 // Allows admins with relevant permissions to have their own ooc colour
|
||||
var/allow_vote_restart = 0 // allow votes to restart
|
||||
@@ -153,7 +154,8 @@ var/list/gamemode_cache = list()
|
||||
|
||||
var/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt
|
||||
var/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt
|
||||
var/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database
|
||||
var/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database
|
||||
var/use_age_restriction_for_antags = 0 //Do antags use account age restrictions? --requires database
|
||||
|
||||
var/simultaneous_pm_warning_timeout = 100
|
||||
|
||||
@@ -268,6 +270,9 @@ var/list/gamemode_cache = list()
|
||||
if ("use_age_restriction_for_jobs")
|
||||
config.use_age_restriction_for_jobs = 1
|
||||
|
||||
if ("use_age_restriction_for_antags")
|
||||
config.use_age_restriction_for_antags = 1
|
||||
|
||||
if ("jobs_have_minimal_access")
|
||||
config.jobs_have_minimal_access = 1
|
||||
|
||||
@@ -319,6 +324,9 @@ var/list/gamemode_cache = list()
|
||||
if ("log_pda")
|
||||
config.log_pda = 1
|
||||
|
||||
if ("log_world_output")
|
||||
config.log_world_output = 1
|
||||
|
||||
if ("log_hrefs")
|
||||
config.log_hrefs = 1
|
||||
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
/**
|
||||
* Startup hook.
|
||||
* Called in world.dm when the server starts.
|
||||
*/
|
||||
/hook/startup
|
||||
|
||||
/**
|
||||
* Roundstart hook.
|
||||
* Called in gameticker.dm when a round starts.
|
||||
*/
|
||||
/hook/roundstart
|
||||
|
||||
/**
|
||||
* Roundend hook.
|
||||
* Called in gameticker.dm when a round ends.
|
||||
*/
|
||||
/hook/roundend
|
||||
|
||||
/**
|
||||
* Death hook.
|
||||
* Called in death.dm when someone dies.
|
||||
* Parameters: var/mob/living/carbon/human, var/gibbed
|
||||
*/
|
||||
/hook/death
|
||||
|
||||
/**
|
||||
* Cloning hook.
|
||||
* Called in cloning.dm when someone is brought back by the wonders of modern science.
|
||||
* Parameters: var/mob/living/carbon/human
|
||||
*/
|
||||
/hook/clone
|
||||
|
||||
/**
|
||||
* Debrained hook.
|
||||
* Called in brain_item.dm when someone gets debrained.
|
||||
* Parameters: var/obj/item/organ/brain
|
||||
*/
|
||||
/hook/debrain
|
||||
|
||||
/**
|
||||
* Borged hook.
|
||||
* Called in robot_parts.dm when someone gets turned into a cyborg.
|
||||
* Parameters: var/mob/living/silicon/robot
|
||||
*/
|
||||
/hook/borgify
|
||||
|
||||
/**
|
||||
* Podman hook.
|
||||
* Called in podmen.dm when someone is brought back as a Diona.
|
||||
* Parameters: var/mob/living/carbon/alien/diona
|
||||
*/
|
||||
/hook/harvest_podman
|
||||
|
||||
/**
|
||||
* Payroll revoked hook.
|
||||
* Called in Accounts_DB.dm when someone's payroll is stolen at the Accounts terminal.
|
||||
* Parameters: var/datum/money_account
|
||||
*/
|
||||
/hook/revoke_payroll
|
||||
|
||||
/**
|
||||
* Account suspension hook.
|
||||
* Called in Accounts_DB.dm when someone's account is suspended or unsuspended at the Accounts terminal.
|
||||
* Parameters: var/datum/money_account
|
||||
*/
|
||||
/hook/change_account_status
|
||||
|
||||
/**
|
||||
* Employee reassignment hook.
|
||||
* Called in card.dm when someone's card is reassigned at the HoP's desk.
|
||||
* Parameters: var/obj/item/weapon/card/id
|
||||
*/
|
||||
/hook/reassign_employee
|
||||
|
||||
/**
|
||||
* Employee terminated hook.
|
||||
* Called in card.dm when someone's card is terminated at the HoP's desk.
|
||||
* Parameters: var/obj/item/weapon/card/id
|
||||
*/
|
||||
/hook/terminate_employee
|
||||
|
||||
/**
|
||||
* Crate sold hook.
|
||||
* Called in supplyshuttle.dm when a crate is sold on the shuttle.
|
||||
* Parameters: var/obj/structure/closet/crate/sold, var/area/shuttle
|
||||
*/
|
||||
/hook/sell_crate
|
||||
/**
|
||||
* Startup hook.
|
||||
* Called in world.dm when the server starts.
|
||||
*/
|
||||
/hook/startup
|
||||
|
||||
/**
|
||||
* Roundstart hook.
|
||||
* Called in gameticker.dm when a round starts.
|
||||
*/
|
||||
/hook/roundstart
|
||||
|
||||
/**
|
||||
* Roundend hook.
|
||||
* Called in gameticker.dm when a round ends.
|
||||
*/
|
||||
/hook/roundend
|
||||
|
||||
/**
|
||||
* Death hook.
|
||||
* Called in death.dm when someone dies.
|
||||
* Parameters: var/mob/living/carbon/human, var/gibbed
|
||||
*/
|
||||
/hook/death
|
||||
|
||||
/**
|
||||
* Cloning hook.
|
||||
* Called in cloning.dm when someone is brought back by the wonders of modern science.
|
||||
* Parameters: var/mob/living/carbon/human
|
||||
*/
|
||||
/hook/clone
|
||||
|
||||
/**
|
||||
* Debrained hook.
|
||||
* Called in brain_item.dm when someone gets debrained.
|
||||
* Parameters: var/obj/item/organ/brain
|
||||
*/
|
||||
/hook/debrain
|
||||
|
||||
/**
|
||||
* Borged hook.
|
||||
* Called in robot_parts.dm when someone gets turned into a cyborg.
|
||||
* Parameters: var/mob/living/silicon/robot
|
||||
*/
|
||||
/hook/borgify
|
||||
|
||||
/**
|
||||
* Podman hook.
|
||||
* Called in podmen.dm when someone is brought back as a Diona.
|
||||
* Parameters: var/mob/living/carbon/alien/diona
|
||||
*/
|
||||
/hook/harvest_podman
|
||||
|
||||
/**
|
||||
* Payroll revoked hook.
|
||||
* Called in Accounts_DB.dm when someone's payroll is stolen at the Accounts terminal.
|
||||
* Parameters: var/datum/money_account
|
||||
*/
|
||||
/hook/revoke_payroll
|
||||
|
||||
/**
|
||||
* Account suspension hook.
|
||||
* Called in Accounts_DB.dm when someone's account is suspended or unsuspended at the Accounts terminal.
|
||||
* Parameters: var/datum/money_account
|
||||
*/
|
||||
/hook/change_account_status
|
||||
|
||||
/**
|
||||
* Employee reassignment hook.
|
||||
* Called in card.dm when someone's card is reassigned at the HoP's desk.
|
||||
* Parameters: var/obj/item/weapon/card/id
|
||||
*/
|
||||
/hook/reassign_employee
|
||||
|
||||
/**
|
||||
* Employee terminated hook.
|
||||
* Called in card.dm when someone's card is terminated at the HoP's desk.
|
||||
* Parameters: var/obj/item/weapon/card/id
|
||||
*/
|
||||
/hook/terminate_employee
|
||||
|
||||
/**
|
||||
* Crate sold hook.
|
||||
* Called in supplyshuttle.dm when a crate is sold on the shuttle.
|
||||
* Parameters: var/obj/structure/closet/crate/sold, var/area/shuttle
|
||||
*/
|
||||
/hook/sell_crate
|
||||
|
||||
31
code/controllers/observer_listener/atom/observer.dm
Normal file
31
code/controllers/observer_listener/atom/observer.dm
Normal file
@@ -0,0 +1,31 @@
|
||||
#define OBSERVER_EVENT_DESTROY "OnDestroy"
|
||||
|
||||
/atom
|
||||
var/list/observer_events
|
||||
|
||||
/atom/Destroy()
|
||||
var/list/destroy_listeners = get_listener_list_from_event(OBSERVER_EVENT_DESTROY)
|
||||
if(destroy_listeners)
|
||||
for(var/destroy_listener in destroy_listeners)
|
||||
call(destroy_listener, destroy_listeners[destroy_listener])(src)
|
||||
|
||||
for(var/list/listeners in observer_events)
|
||||
listeners.Cut()
|
||||
|
||||
return ..()
|
||||
|
||||
/atom/proc/register(var/event, var/procOwner, var/proc_call)
|
||||
var/list/listeners = get_listener_list_from_event(event)
|
||||
listeners[procOwner] = proc_call
|
||||
|
||||
/atom/proc/unregister(var/event, var/procOwner)
|
||||
var/list/listeners = get_listener_list_from_event(event)
|
||||
listeners -= procOwner
|
||||
|
||||
/atom/proc/get_listener_list_from_event(var/observer_event)
|
||||
if(!observer_events) observer_events = list()
|
||||
var/list/listeners = observer_events[observer_event]
|
||||
if(!listeners)
|
||||
listeners = list()
|
||||
observer_events[observer_event] = listeners
|
||||
return listeners
|
||||
28
code/controllers/observer_listener/datum/observer.dm
Normal file
28
code/controllers/observer_listener/datum/observer.dm
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
#define OBSERVER_EVENT_DESTROY "OnDestroy"
|
||||
|
||||
/datum
|
||||
var/list/observer_events
|
||||
|
||||
/datum/Destroy()
|
||||
for(var/list/listeners in observer_events)
|
||||
listeners.Cut()
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/proc/register(var/event, var/procOwner, var/proc_call)
|
||||
var/list/listeners = get_listener_list_from_event(event)
|
||||
listeners[procOwner] = proc_call
|
||||
|
||||
/datum/proc/unregister(var/event, var/procOwner)
|
||||
var/list/listeners = get_listener_list_from_event(event)
|
||||
listeners -= procOwner
|
||||
|
||||
/datum/proc/get_listener_list_from_event(var/observer_event)
|
||||
if(!observer_events) observer_events = list()
|
||||
var/list/listeners = observer_events[observer_event]
|
||||
if(!listeners)
|
||||
listeners = list()
|
||||
observer_events[observer_event] = listeners
|
||||
return listeners
|
||||
*/
|
||||
@@ -1,30 +0,0 @@
|
||||
// We manually initialize the alarm handlers instead of looping over all existing types
|
||||
// to make it possible to write: camera.triggerAlarm() rather than alarm_manager.managers[datum/alarm_handler/camera].triggerAlarm() or a variant thereof.
|
||||
/var/global/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
|
||||
/var/global/datum/alarm_handler/camera/camera_alarm = new()
|
||||
/var/global/datum/alarm_handler/fire/fire_alarm = new()
|
||||
/var/global/datum/alarm_handler/motion/motion_alarm = new()
|
||||
/var/global/datum/alarm_handler/power/power_alarm = new()
|
||||
|
||||
/datum/subsystem/alarm
|
||||
name = "Alarm"
|
||||
var/list/datum/alarm/all_handlers
|
||||
|
||||
/datum/subsystem/alarm/New()
|
||||
all_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, motion_alarm, power_alarm)
|
||||
|
||||
/datum/subsystem/alarm/fire()
|
||||
for(var/datum/alarm_handler/AH in all_handlers)
|
||||
AH.process()
|
||||
|
||||
/datum/subsystem/alarm/proc/active_alarms()
|
||||
var/list/all_alarms = new
|
||||
for(var/datum/alarm_handler/AH in all_handlers)
|
||||
var/list/alarms = AH.alarms
|
||||
all_alarms += alarms
|
||||
|
||||
return all_alarms
|
||||
|
||||
/datum/subsystem/alarm/proc/number_of_active_alarms()
|
||||
var/list/alarms = active_alarms()
|
||||
return alarms.len
|
||||
@@ -25,7 +25,7 @@
|
||||
usr.client.debug_variables(antag)
|
||||
message_admins("Admin [key_name_admin(usr)] is debugging the [antag.role_text] template.")
|
||||
|
||||
/client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano","Chemistry"))
|
||||
/client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano","Chemistry","Wireless"))
|
||||
set category = "Debug"
|
||||
set name = "Debug Controller"
|
||||
set desc = "Debug the various periodic loop controllers for the game (be careful!)"
|
||||
@@ -92,5 +92,8 @@
|
||||
if("Chemistry")
|
||||
debug_variables(chemistryProcess)
|
||||
feedback_add_details("admin_verb", "DChem")
|
||||
if("Wireless")
|
||||
debug_variables(wirelessProcess)
|
||||
feedback_add_details("admin_verb", "DWifi")
|
||||
message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
|
||||
return
|
||||
|
||||
@@ -43,6 +43,20 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
|
||||
group = "Security"
|
||||
hidden = 1
|
||||
|
||||
/datum/supply_packs/forensics
|
||||
name = "Auxiliary forensic tools"
|
||||
contains = list(/obj/item/weapon/forensics/sample_kit,
|
||||
/obj/item/weapon/forensics/sample_kit/powder,
|
||||
/obj/item/weapon/storage/box/swabs,
|
||||
/obj/item/weapon/storage/box/swabs,
|
||||
/obj/item/weapon/storage/box/swabs,
|
||||
/obj/item/weapon/storage/box/slides,
|
||||
/obj/item/weapon/reagent_containers/spray/luminol)
|
||||
cost = 30
|
||||
containertype = /obj/structure/closet/crate
|
||||
containername = "Auxiliary forensic tools"
|
||||
group = "Security"
|
||||
|
||||
/datum/supply_packs/food
|
||||
name = "Kitchen supply crate"
|
||||
contains = list(/obj/item/weapon/reagent_containers/food/condiment/flour,
|
||||
|
||||
58
code/datums/wires/nuclearbomb.dm
Normal file
58
code/datums/wires/nuclearbomb.dm
Normal file
@@ -0,0 +1,58 @@
|
||||
/datum/wires/nuclearbomb
|
||||
holder_type = /obj/machinery/nuclearbomb
|
||||
random = 1
|
||||
wire_count = 7
|
||||
|
||||
var/const/NUCLEARBOMB_WIRE_LIGHT = 1
|
||||
var/const/NUCLEARBOMB_WIRE_TIMING = 2
|
||||
var/const/NUCLEARBOMB_WIRE_SAFETY = 4
|
||||
|
||||
/datum/wires/nuclearbomb/CanUse(var/mob/living/L)
|
||||
var/obj/machinery/nuclearbomb/N = holder
|
||||
return N.panel_open
|
||||
|
||||
/datum/wires/nuclearbomb/GetInteractWindow()
|
||||
var/obj/machinery/nuclearbomb/N = holder
|
||||
. += ..()
|
||||
. += "<BR>The device is [N.timing ? "shaking!" : "still."]<BR>"
|
||||
. += "The device is is [N.safety ? "quiet" : "whirring"].<BR>"
|
||||
. += "The lights are [N.lighthack ? "static" : "functional"].<BR>"
|
||||
|
||||
/datum/wires/nuclearbomb/UpdatePulsed(var/index)
|
||||
var/obj/machinery/nuclearbomb/N = holder
|
||||
switch(index)
|
||||
if(NUCLEARBOMB_WIRE_LIGHT)
|
||||
N.lighthack = !N.lighthack
|
||||
N.update_icon()
|
||||
spawn(100)
|
||||
N.lighthack = !N.lighthack
|
||||
N.update_icon()
|
||||
if(NUCLEARBOMB_WIRE_TIMING)
|
||||
if(N.timing)
|
||||
spawn
|
||||
log_and_message_admins_with_location("pulsed a nuclear bomb's detonation wire, causing it to explode.", holder.x, holder.y, holder.z)
|
||||
N.explode()
|
||||
if(NUCLEARBOMB_WIRE_SAFETY)
|
||||
N.safety = !N.safety
|
||||
spawn(100)
|
||||
N.safety = !N.safety
|
||||
if(N.safety == 1)
|
||||
N.visible_message("<span class='notice'>\The [N] quiets down.</span>")
|
||||
N.secure_device()
|
||||
else
|
||||
N.visible_message("<span class='notice'>\The [N] emits a quiet whirling noise!</span>")
|
||||
|
||||
/datum/wires/nuclearbomb/UpdateCut(var/index, var/mended)
|
||||
var/obj/machinery/nuclearbomb/N = holder
|
||||
switch(index)
|
||||
if(NUCLEARBOMB_WIRE_SAFETY)
|
||||
N.safety = mended
|
||||
if(N.timing)
|
||||
spawn
|
||||
log_and_message_admins_with_location("cut a nuclear bomb's timing wire, causing it to explode.", holder.x, holder.y, holder.z)
|
||||
N.explode()
|
||||
if(NUCLEARBOMB_WIRE_TIMING)
|
||||
N.secure_device()
|
||||
if(NUCLEARBOMB_WIRE_LIGHT)
|
||||
N.lighthack = !mended
|
||||
N.update_icon()
|
||||
@@ -133,13 +133,6 @@
|
||||
name = "disk"
|
||||
icon = 'icons/obj/items.dmi'
|
||||
|
||||
/obj/item/weapon/disk/nuclear
|
||||
name = "nuclear authentication disk"
|
||||
desc = "Better keep this safe."
|
||||
icon_state = "nucleardisk"
|
||||
item_state = "card-id"
|
||||
w_class = 2.0
|
||||
|
||||
/*
|
||||
/obj/item/weapon/game_kit
|
||||
name = "Gaming Kit"
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
var/mob_path = /mob/living/carbon/human // Mobtype this antag will use if none is provided.
|
||||
var/feedback_tag = "traitor_objective" // End of round
|
||||
var/bantype = "Syndicate" // Ban to check when spawning this antag.
|
||||
var/minimum_player_age = 7 // Players need to be at least minimum_player_age days old before they are eligable for auto-spawning
|
||||
var/suspicion_chance = 50 // Prob of being on the initial Command report
|
||||
var/flags = 0 // Various runtime options.
|
||||
|
||||
@@ -95,6 +96,8 @@
|
||||
for(var/datum/mind/player in ticker.mode.get_players_for_role(role_type, id))
|
||||
if(ghosts_only && !istype(player.current, /mob/dead))
|
||||
log_debug("[key_name(player)] is not eligible to become a [role_text]: Only ghosts may join as this role!")
|
||||
else if(config.use_age_restriction_for_antags && player.current.client.player_age < minimum_player_age)
|
||||
log_debug("[key_name(player)] is not eligible to become a [role_text]: Is only [player.current.client.player_age] day\s old, has to be [minimum_player_age] day\s!")
|
||||
else if(player.special_role)
|
||||
log_debug("[key_name(player)] is not eligible to become a [role_text]: They already have a special role ([player.special_role])!")
|
||||
else if (player in pending_antagonists)
|
||||
@@ -198,10 +201,10 @@
|
||||
for(var/datum/mind/player in pending_antagonists)
|
||||
pending_antagonists -= player
|
||||
add_antagonist(player,0,0,1)
|
||||
|
||||
|
||||
reset_antag_selection()
|
||||
|
||||
//Resets the antag selection, clearing all pending_antagonists and their special_role
|
||||
//Resets the antag selection, clearing all pending_antagonists and their special_role
|
||||
//(and assigned_role if ANTAG_OVERRIDE_JOB is set) as well as clearing the candidate list.
|
||||
//Existing antagonists are left untouched.
|
||||
/datum/antagonist/proc/reset_antag_selection()
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
var/list/fingerprintshidden
|
||||
var/fingerprintslast = null
|
||||
var/list/blood_DNA
|
||||
var/was_bloodied
|
||||
var/blood_color
|
||||
var/last_bumped = 0
|
||||
var/pass_flags = 0
|
||||
var/throwpass = 0
|
||||
var/germ_level = GERM_LEVEL_AMBIENT // The higher the germ level, the more germ on the atom.
|
||||
var/simulated = 1 //filter for actions - used by lighting overlays
|
||||
var/fluorescent // Shows up under a UV light.
|
||||
|
||||
///Chemistry.
|
||||
var/datum/reagents/reagents = null
|
||||
@@ -23,6 +25,9 @@
|
||||
//Detective Work, used for the duplicate data points kept in the scanners
|
||||
var/list/original_atom
|
||||
|
||||
/atom/proc/reveal_blood()
|
||||
return
|
||||
|
||||
/atom/proc/assume_air(datum/gas_mixture/giver)
|
||||
return null
|
||||
|
||||
@@ -304,7 +309,7 @@ its easier to just keep the beam vertical.
|
||||
fingerprints = list()
|
||||
|
||||
//Hash this shit.
|
||||
var/full_print = md5(H.dna.uni_identity)
|
||||
var/full_print = H.get_full_print()
|
||||
|
||||
// Add the fingerprints
|
||||
//
|
||||
@@ -387,6 +392,7 @@ its easier to just keep the beam vertical.
|
||||
if(!blood_DNA || !istype(blood_DNA, /list)) //if our list of DNA doesn't exist yet (or isn't a list) initialise it.
|
||||
blood_DNA = list()
|
||||
|
||||
was_bloodied = 1
|
||||
blood_color = "#A10808"
|
||||
if(istype(M))
|
||||
if (!istype(M.dna, /datum/dna))
|
||||
@@ -406,10 +412,10 @@ its easier to just keep the beam vertical.
|
||||
if(toxvomit)
|
||||
this.icon_state = "vomittox_[pick(1,4)]"
|
||||
|
||||
|
||||
/atom/proc/clean_blood()
|
||||
if(!simulated)
|
||||
return
|
||||
fluorescent = 0
|
||||
src.germ_level = 0
|
||||
if(istype(blood_DNA, /list))
|
||||
blood_DNA = null
|
||||
|
||||
@@ -326,8 +326,11 @@ var/global/datum/controller/gameticker/ticker
|
||||
spawn(50)
|
||||
callHook("roundend")
|
||||
|
||||
if (mode.station_was_nuked)
|
||||
feedback_set_details("end_proper","nuke")
|
||||
if (universe_has_ended)
|
||||
if(mode.station_was_nuked)
|
||||
feedback_set_details("end_proper","nuke")
|
||||
else
|
||||
feedback_set_details("end_proper","universe destroyed")
|
||||
if(!delay_end)
|
||||
world << "<span class='notice'><b>Rebooting due to destruction of station in [restart_timeout/10] seconds</b></span>"
|
||||
else
|
||||
|
||||
@@ -111,12 +111,11 @@
|
||||
H.equip_to_slot_or_del(new /obj/item/weapon/flame/lighter/zippo(H), slot_l_store)
|
||||
if(H.backbag == 1)//Why cant some of these things spawn in his office?
|
||||
H.equip_to_slot_or_del(new /obj/item/weapon/storage/box/evidence(H), slot_l_hand)
|
||||
H.equip_to_slot_or_del(new /obj/item/device/detective_scanner(H), slot_r_store)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/weapon/storage/box/evidence(H), slot_in_backpack)
|
||||
H.equip_to_slot_or_del(new /obj/item/device/detective_scanner(H), slot_in_backpack)
|
||||
if(H.mind.role_alt_title && H.mind.role_alt_title == "Forensic Technician")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/forensics/blue(H), slot_wear_suit)
|
||||
H.equip_to_slot_or_del(new /obj/item/weapon/storage/briefcase/crimekit, slot_r_hand)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/det_trench(H), slot_wear_suit)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/head/det(H), slot_head)
|
||||
|
||||
@@ -5,16 +5,127 @@
|
||||
desc = "A remote control switch for something."
|
||||
var/id = null
|
||||
var/active = 0
|
||||
var/operating = 0
|
||||
anchored = 1.0
|
||||
use_power = 1
|
||||
idle_power_usage = 2
|
||||
active_power_usage = 4
|
||||
var/_wifi_id
|
||||
var/datum/wifi/sender/button/wifi_sender
|
||||
|
||||
/obj/machinery/button/initialize()
|
||||
..()
|
||||
update_icon()
|
||||
if(_wifi_id)
|
||||
wifi_sender = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/button/Destroy()
|
||||
qdel(wifi_sender)
|
||||
wifi_sender = null
|
||||
return..()
|
||||
|
||||
/obj/machinery/button/attack_ai(mob/user as mob)
|
||||
return src.attack_hand(user)
|
||||
return attack_hand(user)
|
||||
|
||||
/obj/machinery/button/attackby(obj/item/weapon/W, mob/user as mob)
|
||||
if(istype(W, /obj/item/device/detective_scanner))
|
||||
return attack_hand(user)
|
||||
|
||||
/obj/machinery/button/attack_hand(mob/living/user)
|
||||
..()
|
||||
activate(user)
|
||||
|
||||
/obj/machinery/button/proc/activate(mob/living/user)
|
||||
if(operating || !istype(wifi_sender))
|
||||
return
|
||||
return src.attack_hand(user)
|
||||
|
||||
operating = 1
|
||||
active = 1
|
||||
use_power(5)
|
||||
update_icon()
|
||||
wifi_sender.activate(user)
|
||||
sleep(10)
|
||||
active = 0
|
||||
update_icon()
|
||||
operating = 0
|
||||
|
||||
/obj/machinery/button/update_icon()
|
||||
if(active)
|
||||
icon_state = "launcheract"
|
||||
else
|
||||
icon_state = "launcherbtt"
|
||||
|
||||
//alternate button with the same functionality, except has a lightswitch sprite instead
|
||||
/obj/machinery/button/alternate
|
||||
icon = 'icons/obj/power.dmi'
|
||||
icon_state = "light0"
|
||||
|
||||
/obj/machinery/button/alternate/update_icon()
|
||||
icon_state = "light[active]"
|
||||
|
||||
//Toggle button with two states (on and off) and calls seperate procs for each state
|
||||
/obj/machinery/button/toggle/activate(mob/living/user)
|
||||
if(operating || !istype(wifi_sender))
|
||||
return
|
||||
|
||||
operating = 1
|
||||
active = !active
|
||||
use_power(5)
|
||||
if(active)
|
||||
wifi_sender.activate(user)
|
||||
else
|
||||
wifi_sender.deactivate(user)
|
||||
update_icon()
|
||||
operating = 0
|
||||
|
||||
/obj/machinery/button/toggle/alternate
|
||||
icon = 'icons/obj/power.dmi'
|
||||
icon_state = "light0"
|
||||
|
||||
/obj/machinery/button/toggle/alternate/update_icon()
|
||||
icon_state = "light[active]"
|
||||
|
||||
|
||||
//-------------------------------
|
||||
// Mass Driver Button
|
||||
// Passes the activate call to a mass driver wifi sender
|
||||
//-------------------------------
|
||||
/obj/machinery/button/mass_driver
|
||||
var/datum/wifi/sender/mass_driver/sender
|
||||
|
||||
/obj/machinery/button/mass_driver/initialize()
|
||||
..()
|
||||
sender = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/button/mass_driver/activate(mob/living/user)
|
||||
if(active || !istype(wifi_sender))
|
||||
return
|
||||
active = 1
|
||||
use_power(5)
|
||||
update_icon()
|
||||
sender.cycle()
|
||||
active = 0
|
||||
update_icon()
|
||||
|
||||
//-------------------------------
|
||||
// Door Button
|
||||
//-------------------------------
|
||||
/obj/machinery/button/toggle/door
|
||||
var/datum/wifi/sender/door/sender
|
||||
|
||||
/obj/machinery/button/toggle/door/initialize()
|
||||
..()
|
||||
sender = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/button/toggle/door/activate(mob/living/user)
|
||||
if(operating || !istype(sender))
|
||||
return
|
||||
|
||||
operating = 1
|
||||
active = !active
|
||||
use_power(5)
|
||||
update_icon()
|
||||
if(!active)
|
||||
sender.open()
|
||||
else
|
||||
sender.close()
|
||||
operating = 0
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
else
|
||||
H.dna = R.dna
|
||||
H.UpdateAppearance()
|
||||
H.sync_organ_dna()
|
||||
if(heal_level < 60)
|
||||
randmutb(H) //Sometimes the clones come out wrong.
|
||||
H.dna.UpdateSE()
|
||||
@@ -254,7 +255,7 @@
|
||||
locked = 0
|
||||
go_out()
|
||||
return 1
|
||||
|
||||
|
||||
//Put messages in the connected computer's temp var for display.
|
||||
/obj/machinery/clonepod/proc/connected_message(var/message)
|
||||
if((isnull(connected)) || (!istype(connected, /obj/machinery/computer/cloning)))
|
||||
|
||||
@@ -96,15 +96,6 @@
|
||||
text = replacetext(text, "\n", "<BR>")
|
||||
return text
|
||||
|
||||
|
||||
/obj/machinery/computer/attack_ghost(user as mob)
|
||||
return src.attack_hand(user)
|
||||
|
||||
/obj/machinery/computer/attack_hand(user as mob)
|
||||
/* Observers can view computers, but not actually use them via Topic*/
|
||||
if(istype(user, /mob/dead/observer)) return 0
|
||||
return ..()
|
||||
|
||||
/obj/machinery/computer/attackby(I as obj, user as mob)
|
||||
if(istype(I, /obj/item/weapon/screwdriver) && circuit)
|
||||
playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
monitor_type = /datum/nano_module/alarm_monitor/all
|
||||
circuit = /obj/item/weapon/circuitboard/stationalert_all
|
||||
|
||||
/obj/machinery/computer/station_alert/New()
|
||||
/obj/machinery/computer/station_alert/initialize()
|
||||
..()
|
||||
alarm_monitor = new monitor_type(src)
|
||||
alarm_monitor.register(src, /obj/machinery/computer/station_alert/update_icon)
|
||||
|
||||
@@ -29,16 +29,14 @@
|
||||
|
||||
|
||||
/obj/machinery/lapvend/attackby(obj/item/weapon/W as obj, mob/user as mob)
|
||||
if(vendmode == 1)
|
||||
if(istype(W, /obj/item/weapon/card))
|
||||
var/obj/item/weapon/card/I = W
|
||||
scan_card(I)
|
||||
var/obj/item/weapon/card/id/I = W.GetID()
|
||||
|
||||
if(vendmode == 1 && I)
|
||||
scan_id(I, W)
|
||||
vendmode = 0
|
||||
if(vendmode == 3 && I)
|
||||
if(reimburse_id(I, W))
|
||||
vendmode = 0
|
||||
if(vendmode == 3)
|
||||
if(istype(W,/obj/item/weapon/card))
|
||||
var/obj/item/weapon/card/I = W
|
||||
if(reimburse(I))
|
||||
vendmode = 0
|
||||
if(vendmode == 0)
|
||||
if(istype(W, /obj/item/device/laptop))
|
||||
var/obj/item/device/laptop/L = W
|
||||
@@ -201,26 +199,24 @@
|
||||
|
||||
newlap.spawn_parts()
|
||||
|
||||
/obj/machinery/lapvend/proc/scan_card(var/obj/item/weapon/card/I)
|
||||
if (istype(I, /obj/item/weapon/card/id))
|
||||
var/obj/item/weapon/card/id/C = I
|
||||
visible_message("<span class='info'>[usr] swipes a card through [src].</span>")
|
||||
var/datum/money_account/CH = get_account(C.associated_account_number)
|
||||
if(!CH)
|
||||
usr << "\icon[src]<span class='warning'>No valid account number is associated with this card.</span>"
|
||||
return
|
||||
if(CH.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2)
|
||||
if(vendor_account)
|
||||
var/attempt_pin = input("Enter pin code", "Vendor transaction") as num
|
||||
var/datum/money_account/D = attempt_account_access(C.associated_account_number, attempt_pin, 2)
|
||||
if(D)
|
||||
transfer_and_vend(D, C)
|
||||
else
|
||||
usr << "\icon[src]<span class='warning'>Unable to access account. Check security settings and try again.</span>"
|
||||
/obj/machinery/lapvend/proc/scan_id(var/obj/item/weapon/card/id/C, var/obj/item/I)
|
||||
visible_message("<span class='info'>\The [usr] swipes \the [I] through \the [src].</span>")
|
||||
var/datum/money_account/CH = get_account(C.associated_account_number)
|
||||
if(!CH)
|
||||
usr << "\icon[src]<span class='warning'>No valid account number is associated with this card.</span>"
|
||||
return
|
||||
if(CH.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2)
|
||||
if(vendor_account)
|
||||
var/attempt_pin = input("Enter pin code", "Vendor transaction") as num
|
||||
var/datum/money_account/D = attempt_account_access(C.associated_account_number, attempt_pin, 2)
|
||||
if(D)
|
||||
transfer_and_vend(D, C)
|
||||
else
|
||||
usr << "\icon[src]<span class='warning'>Unable to access vendor account. Please record the machine ID and call [boss_short] Support.</span>"
|
||||
else
|
||||
transfer_and_vend(CH, C)
|
||||
usr << "\icon[src]<span class='warning'>Unable to access vendor account. Please record the machine ID and call CentComm Support.</span>"
|
||||
else
|
||||
transfer_and_vend(CH, C)
|
||||
|
||||
|
||||
// Transfers money and vends the laptop.
|
||||
@@ -352,30 +348,28 @@
|
||||
|
||||
|
||||
|
||||
/obj/machinery/lapvend/proc/reimburse(var/obj/item/weapon/card/I)
|
||||
if (istype(I, /obj/item/weapon/card/id))
|
||||
var/obj/item/weapon/card/id/C = I
|
||||
visible_message("<span class='info'>[usr] swipes a card through [src].</span>")
|
||||
var/datum/money_account/CH = get_account(C.associated_account_number)
|
||||
if(!CH)
|
||||
usr << "\icon[src]<span class='warning'>No valid account number is associated with this card.</span>"
|
||||
return 0
|
||||
if(CH.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2)
|
||||
if(vendor_account)
|
||||
var/attempt_pin = input("Enter pin code", "Vendor transaction") as num
|
||||
var/datum/money_account/D = attempt_account_access(C.associated_account_number, attempt_pin, 2)
|
||||
if(D)
|
||||
transfer_and_reimburse(D)
|
||||
return 1
|
||||
else
|
||||
usr << "\icon[src]<span class='warning'>Unable to access account. Check security settings and try again.</span>"
|
||||
return 0
|
||||
/obj/machinery/lapvend/proc/reimburse_id(var/obj/item/weapon/card/id/C, var/obj/item/I)
|
||||
visible_message("<span class='info'>\The [usr] swipes \the [I] through \the [src].</span>")
|
||||
var/datum/money_account/CH = get_account(C.associated_account_number)
|
||||
if(!CH)
|
||||
usr << "\icon[src]<span class='warning'>No valid account number is associated with this card.</span>"
|
||||
return 0
|
||||
if(CH.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2)
|
||||
if(vendor_account)
|
||||
var/attempt_pin = input("Enter pin code", "Vendor transaction") as num
|
||||
var/datum/money_account/D = attempt_account_access(C.associated_account_number, attempt_pin, 2)
|
||||
if(D)
|
||||
transfer_and_reimburse(D)
|
||||
return 1
|
||||
else
|
||||
usr << "\icon[src]<span class='warning'>Unable to access vendor account. Please record the machine ID and call [boss_short] Support.</span>"
|
||||
return 0
|
||||
else
|
||||
transfer_and_reimburse(CH)
|
||||
return 1
|
||||
usr << "\icon[src]<span class='warning'>Unable to access vendor account. Please record the machine ID and call CentComm Support.</span>"
|
||||
return 0
|
||||
else
|
||||
transfer_and_reimburse(CH)
|
||||
return 1
|
||||
|
||||
/obj/machinery/lapvend/proc/transfer_and_reimburse(var/datum/money_account/D)
|
||||
var/transaction_amount = total()
|
||||
|
||||
@@ -24,22 +24,6 @@
|
||||
user << "Error, no route to host."
|
||||
|
||||
/obj/machinery/button/remote/attackby(obj/item/weapon/W, mob/user as mob)
|
||||
/* For later implementation
|
||||
if (istype(W, /obj/item/weapon/screwdriver))
|
||||
{
|
||||
if(wiresexposed)
|
||||
icon_state = "doorctrl0"
|
||||
wiresexposed = 0
|
||||
|
||||
else
|
||||
icon_state = "doorctrl-open"
|
||||
wiresexposed = 1
|
||||
|
||||
return
|
||||
}
|
||||
*/
|
||||
if(istype(W, /obj/item/device/detective_scanner))
|
||||
return
|
||||
return src.attack_hand(user)
|
||||
|
||||
/obj/machinery/button/remote/emag_act(var/remaining_charges, var/mob/user)
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
var/open_sound_powered = 'sound/machines/airlock.ogg'
|
||||
var/open_sound_unpowered = 'sound/machines/airlock_creaking.ogg'
|
||||
|
||||
var/_wifi_id
|
||||
var/datum/wifi/receiver/button/door/wifi_receiver
|
||||
|
||||
/obj/machinery/door/airlock/attack_generic(var/mob/user, var/damage)
|
||||
if(stat & (BROKEN|NOPOWER))
|
||||
if(damage >= 10)
|
||||
@@ -541,7 +544,7 @@ About the new airlock wires panel:
|
||||
/obj/machinery/door/airlock/attack_ai(mob/user as mob)
|
||||
ui_interact(user)
|
||||
|
||||
/obj/machinery/door/airlock/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
|
||||
/obj/machinery/door/airlock/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
|
||||
var/data[0]
|
||||
|
||||
data["main_power_loss"] = round(main_power_lost_until > 0 ? max(main_power_lost_until - world.time, 0) / 10 : main_power_lost_until, 1)
|
||||
@@ -561,7 +564,7 @@ About the new airlock wires panel:
|
||||
|
||||
ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
|
||||
if (!ui)
|
||||
ui = new(user, src, ui_key, "door_control.tmpl", "Door Controls", 450, 350)
|
||||
ui = new(user, src, ui_key, "door_control.tmpl", "Door Controls", 450, 350, state = state)
|
||||
ui.set_initial_data(data)
|
||||
ui.open()
|
||||
ui.set_auto_update(1)
|
||||
@@ -656,13 +659,10 @@ About the new airlock wires panel:
|
||||
return
|
||||
|
||||
/obj/machinery/door/airlock/CanUseTopic(var/mob/user)
|
||||
if(issilicon(user))
|
||||
return STATUS_CLOSE
|
||||
|
||||
if(operating < 0) //emagged
|
||||
user << "<span class='warning'>Unable to interface: Internal error.</span>"
|
||||
return STATUS_CLOSE
|
||||
if(!src.canAIControl())
|
||||
if(issilicon(user) && !src.canAIControl())
|
||||
if(src.canAIHack(user))
|
||||
src.hack(user)
|
||||
else
|
||||
@@ -738,7 +738,7 @@ About the new airlock wires panel:
|
||||
if(src.isElectrified())
|
||||
if(src.shock(user, 75))
|
||||
return
|
||||
if(istype(C, /obj/item/device/detective_scanner) || istype(C, /obj/item/taperoll))
|
||||
if(istype(C, /obj/item/taperoll))
|
||||
return
|
||||
|
||||
src.add_fingerprint(user)
|
||||
@@ -1040,9 +1040,15 @@ About the new airlock wires panel:
|
||||
src.closeOther = A
|
||||
break
|
||||
|
||||
//wireless connection
|
||||
if(_wifi_id)
|
||||
wifi_receiver = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/door/airlock/Destroy()
|
||||
qdel(wires)
|
||||
wires = null
|
||||
qdel(wifi_receiver)
|
||||
wifi_receiver = null
|
||||
return ..()
|
||||
|
||||
// Most doors will never be deconstructed over the course of a round,
|
||||
|
||||
@@ -28,6 +28,19 @@
|
||||
//turning this off prevents awkward zone geometry in places like medbay lobby, for example.
|
||||
block_air_zones = 0
|
||||
|
||||
var/_wifi_id
|
||||
var/datum/wifi/receiver/button/door/wifi_receiver
|
||||
|
||||
/obj/machinery/door/blast/initialize()
|
||||
..()
|
||||
if(_wifi_id)
|
||||
wifi_receiver = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/door/airlock/Destroy()
|
||||
qdel(wifi_receiver)
|
||||
wifi_receiver = null
|
||||
return ..()
|
||||
|
||||
// Proc: Bumped()
|
||||
// Parameters: 1 (AM - Atom that tried to walk through this object)
|
||||
// Description: If we are open returns zero, otherwise returns result of parent function.
|
||||
@@ -144,7 +157,7 @@
|
||||
if(stat & BROKEN)
|
||||
stat &= ~BROKEN
|
||||
|
||||
|
||||
|
||||
/obj/machinery/door/blast/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
|
||||
if(air_group) return 1
|
||||
return ..()
|
||||
|
||||
@@ -202,8 +202,6 @@
|
||||
..()
|
||||
|
||||
/obj/machinery/door/attackby(obj/item/I as obj, mob/user as mob)
|
||||
if(istype(I, /obj/item/device/detective_scanner))
|
||||
return
|
||||
src.add_fingerprint(user)
|
||||
|
||||
if(istype(I, /obj/item/stack/material) && I.get_material_name() == src.get_material_name())
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
use_power = 1
|
||||
idle_power_usage = 2
|
||||
flags = PROXMOVE
|
||||
var/_wifi_id
|
||||
var/datum/wifi/receiver/button/flasher/wifi_receiver
|
||||
|
||||
/obj/machinery/flasher/portable //Portable version of the flasher. Only flashes when anchored
|
||||
name = "portable flasher"
|
||||
@@ -25,6 +27,16 @@
|
||||
base_state = "pflash"
|
||||
density = 1
|
||||
|
||||
/obj/machinery/flasher/initialize()
|
||||
..()
|
||||
if(_wifi_id)
|
||||
wifi_receiver = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/flasher/Destroy()
|
||||
qdel(wifi_receiver)
|
||||
wifi_receiver = null
|
||||
return ..()
|
||||
|
||||
/obj/machinery/flasher/power_change()
|
||||
..()
|
||||
if ( !(stat & NOPOWER) )
|
||||
|
||||
@@ -12,6 +12,18 @@
|
||||
var/lit = 0
|
||||
var/id = null
|
||||
var/on_icon = "sign_on"
|
||||
var/_wifi_id
|
||||
var/datum/wifi/receiver/button/holosign/wifi_receiver
|
||||
|
||||
/obj/machinery/holosign/initialize()
|
||||
..()
|
||||
if(_wifi_id)
|
||||
wifi_receiver = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/holosign/Destroy()
|
||||
qdel(wifi_receiver)
|
||||
wifi_receiver = null
|
||||
return ..()
|
||||
|
||||
/obj/machinery/holosign/proc/toggle()
|
||||
if (stat & (BROKEN|NOPOWER))
|
||||
|
||||
@@ -4,11 +4,32 @@
|
||||
icon = 'icons/obj/stationobjs.dmi'
|
||||
icon_state = "igniter1"
|
||||
var/id = null
|
||||
var/on = 1.0
|
||||
anchored = 1.0
|
||||
var/on = 0
|
||||
anchored = 1
|
||||
use_power = 1
|
||||
idle_power_usage = 2
|
||||
active_power_usage = 4
|
||||
var/_wifi_id
|
||||
var/datum/wifi/receiver/button/igniter/wifi_receiver
|
||||
|
||||
/obj/machinery/igniter/New()
|
||||
..()
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/igniter/initialize()
|
||||
..()
|
||||
update_icon()
|
||||
if(_wifi_id)
|
||||
wifi_receiver = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/igniter/update_icon()
|
||||
..()
|
||||
icon_state = "igniter[on]"
|
||||
|
||||
/obj/machinery/igniter/Destroy()
|
||||
qdel(wifi_receiver)
|
||||
wifi_receiver = null
|
||||
return ..()
|
||||
|
||||
/obj/machinery/igniter/attack_ai(mob/user as mob)
|
||||
return src.attack_hand(user)
|
||||
@@ -17,29 +38,25 @@
|
||||
if(..())
|
||||
return
|
||||
add_fingerprint(user)
|
||||
|
||||
use_power(50)
|
||||
src.on = !( src.on )
|
||||
src.icon_state = text("igniter[]", src.on)
|
||||
ignite()
|
||||
return
|
||||
|
||||
/obj/machinery/igniter/process() //ugh why is this even in process()?
|
||||
if (src.on && !(stat & NOPOWER) )
|
||||
if (on && powered() )
|
||||
var/turf/location = src.loc
|
||||
if (isturf(location))
|
||||
location.hotspot_expose(1000,500,1)
|
||||
return 1
|
||||
|
||||
/obj/machinery/igniter/New()
|
||||
..()
|
||||
icon_state = "igniter[on]"
|
||||
|
||||
/obj/machinery/igniter/power_change()
|
||||
..()
|
||||
if(!( stat & NOPOWER) )
|
||||
icon_state = "igniter[src.on]"
|
||||
else
|
||||
icon_state = "igniter0"
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/igniter/proc/ignite()
|
||||
use_power(50)
|
||||
on = !on
|
||||
update_icon()
|
||||
|
||||
|
||||
// Wall mounted remote-control igniter.
|
||||
|
||||
@@ -56,52 +73,59 @@
|
||||
use_power = 1
|
||||
idle_power_usage = 2
|
||||
active_power_usage = 4
|
||||
var/_wifi_id
|
||||
var/datum/wifi/receiver/button/sparker/wifi_receiver
|
||||
|
||||
|
||||
/obj/machinery/sparker/New()
|
||||
/obj/machinery/sparker/initialize()
|
||||
..()
|
||||
if(_wifi_id)
|
||||
wifi_receiver = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/sparker/Destroy()
|
||||
qdel(wifi_receiver)
|
||||
wifi_receiver = null
|
||||
return ..()
|
||||
|
||||
/obj/machinery/sparker/update_icon()
|
||||
..()
|
||||
if(disable)
|
||||
icon_state = "migniter-d"
|
||||
else if(powered())
|
||||
icon_state = "migniter"
|
||||
// src.sd_SetLuminosity(2)
|
||||
else
|
||||
icon_state = "migniter-p"
|
||||
// src.sd_SetLuminosity(0)
|
||||
|
||||
/obj/machinery/sparker/power_change()
|
||||
..()
|
||||
if ( !(stat & NOPOWER) && disable == 0 )
|
||||
|
||||
icon_state = "[base_state]"
|
||||
// src.sd_SetLuminosity(2)
|
||||
else
|
||||
icon_state = "[base_state]-p"
|
||||
// src.sd_SetLuminosity(0)
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/sparker/attackby(obj/item/weapon/W as obj, mob/user as mob)
|
||||
if(istype(W, /obj/item/device/detective_scanner))
|
||||
return
|
||||
if (istype(W, /obj/item/weapon/screwdriver))
|
||||
add_fingerprint(user)
|
||||
src.disable = !src.disable
|
||||
if (src.disable)
|
||||
disable = !disable
|
||||
if(disable)
|
||||
user.visible_message("<span class='warning'>[user] has disabled the [src]!</span>", "<span class='warning'>You disable the connection to the [src].</span>")
|
||||
icon_state = "[base_state]-d"
|
||||
if (!src.disable)
|
||||
else if(!disable)
|
||||
user.visible_message("<span class='warning'>[user] has reconnected the [src]!</span>", "<span class='warning'>You fix the connection to the [src].</span>")
|
||||
if(src.powered())
|
||||
icon_state = "[base_state]"
|
||||
else
|
||||
icon_state = "[base_state]-p"
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/sparker/attack_ai()
|
||||
if (src.anchored)
|
||||
return src.ignite()
|
||||
if (anchored)
|
||||
return ignite()
|
||||
else
|
||||
return
|
||||
|
||||
/obj/machinery/sparker/proc/ignite()
|
||||
if (!(powered()))
|
||||
if (!powered())
|
||||
return
|
||||
|
||||
if ((src.disable) || (src.last_spark && world.time < src.last_spark + 50))
|
||||
if (disable || (last_spark && world.time < last_spark + 50))
|
||||
return
|
||||
|
||||
|
||||
flick("[base_state]-spark", src)
|
||||
flick("migniter-spark", src)
|
||||
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
|
||||
s.set_up(2, 1, src)
|
||||
s.start()
|
||||
@@ -134,15 +158,13 @@
|
||||
icon_state = "launcheract"
|
||||
|
||||
for(var/obj/machinery/sparker/M in machines)
|
||||
if (M.id == src.id)
|
||||
if (M.id == id)
|
||||
spawn( 0 )
|
||||
M.ignite()
|
||||
|
||||
for(var/obj/machinery/igniter/M in machines)
|
||||
if(M.id == src.id)
|
||||
use_power(50)
|
||||
M.on = !( M.on )
|
||||
M.icon_state = text("igniter[]", M.on)
|
||||
if(M.id == id)
|
||||
M.ignite()
|
||||
|
||||
sleep(50)
|
||||
|
||||
|
||||
@@ -14,7 +14,18 @@
|
||||
var/code = 1.0
|
||||
var/id = 1.0
|
||||
var/drive_range = 50 //this is mostly irrelevant since current mass drivers throw into space, but you could make a lower-range mass driver for interstation transport or something I guess.
|
||||
var/_wifi_id
|
||||
var/datum/wifi/receiver/button/mass_driver/wifi_receiver
|
||||
|
||||
/obj/machinery/mass_driver/initialize()
|
||||
..()
|
||||
if(_wifi_id)
|
||||
wifi_receiver = new(_wifi_id, src)
|
||||
|
||||
/obj/machinery/mass_driver/Destroy()
|
||||
qdel(wifi_receiver)
|
||||
wifi_receiver = null
|
||||
return ..()
|
||||
|
||||
/obj/machinery/mass_driver/proc/drive(amount)
|
||||
if(stat & (BROKEN|NOPOWER))
|
||||
|
||||
@@ -6,85 +6,69 @@ var/bomb_set
|
||||
icon = 'icons/obj/stationobjs.dmi'
|
||||
icon_state = "nuclearbomb0"
|
||||
density = 1
|
||||
var/deployable = 0.0
|
||||
var/extended = 0.0
|
||||
var/deployable = 0
|
||||
var/extended = 0
|
||||
var/lighthack = 0
|
||||
var/opened = 0.0
|
||||
var/timeleft = 60.0
|
||||
var/timing = 0.0
|
||||
var/timeleft = 120
|
||||
var/timing = 0
|
||||
var/r_code = "ADMIN"
|
||||
var/code = ""
|
||||
var/yes_code = 0.0
|
||||
var/safety = 1.0
|
||||
var/yes_code = 0
|
||||
var/safety = 1
|
||||
var/obj/item/weapon/disk/nuclear/auth = null
|
||||
var/list/wires = list()
|
||||
var/light_wire
|
||||
var/safety_wire
|
||||
var/timing_wire
|
||||
var/removal_stage = 0 // 0 is no removal, 1 is covers removed, 2 is covers open,
|
||||
// 3 is sealant open, 4 is unwrenched, 5 is removed from bolts.
|
||||
var/removal_stage = 0 // 0 is no removal, 1 is covers removed, 2 is covers open, 3 is sealant open, 4 is unwrenched, 5 is removed from bolts.
|
||||
var/lastentered
|
||||
use_power = 0
|
||||
|
||||
|
||||
unacidable = 1
|
||||
var/previous_level = ""
|
||||
var/datum/wires/nuclearbomb/wires = null
|
||||
|
||||
/obj/machinery/nuclearbomb/New()
|
||||
..()
|
||||
r_code = "[rand(10000, 99999.0)]"//Creates a random code upon object spawn.
|
||||
wires = new/datum/wires/nuclearbomb(src)
|
||||
|
||||
src.wires["Red"] = 0
|
||||
src.wires["Blue"] = 0
|
||||
src.wires["Green"] = 0
|
||||
src.wires["Marigold"] = 0
|
||||
src.wires["Fuschia"] = 0
|
||||
src.wires["Black"] = 0
|
||||
src.wires["Pearl"] = 0
|
||||
var/list/w = list("Red","Blue","Green","Marigold","Black","Fuschia","Pearl")
|
||||
src.light_wire = pick(w)
|
||||
w -= src.light_wire
|
||||
src.timing_wire = pick(w)
|
||||
w -= src.timing_wire
|
||||
src.safety_wire = pick(w)
|
||||
w -= src.safety_wire
|
||||
/obj/machinery/nuclearbomb/Destroy()
|
||||
qdel(wires)
|
||||
wires = null
|
||||
return ..()
|
||||
|
||||
/obj/machinery/nuclearbomb/process()
|
||||
if (src.timing)
|
||||
bomb_set = 1 //So long as there is one nuke timing, it means one nuke is armed.
|
||||
src.timeleft--
|
||||
if (src.timeleft <= 0)
|
||||
explode()
|
||||
for(var/mob/M in viewers(1, src))
|
||||
if ((M.client && M.machine == src))
|
||||
src.attack_hand(M)
|
||||
src.timeleft = max(timeleft - 2, 0) // 2 seconds per process()
|
||||
if (timeleft <= 0)
|
||||
spawn
|
||||
explode()
|
||||
nanomanager.update_uis(src)
|
||||
return
|
||||
|
||||
/obj/machinery/nuclearbomb/attackby(obj/item/weapon/O as obj, mob/user as mob)
|
||||
|
||||
/obj/machinery/nuclearbomb/attackby(obj/item/weapon/O as obj, mob/user as mob, params)
|
||||
if (istype(O, /obj/item/weapon/screwdriver))
|
||||
src.add_fingerprint(user)
|
||||
if (src.auth)
|
||||
if (src.opened == 0)
|
||||
src.opened = 1
|
||||
if (panel_open == 0)
|
||||
panel_open = 1
|
||||
overlays += image(icon, "npanel_open")
|
||||
user << "You unscrew the control panel of [src]."
|
||||
|
||||
playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
|
||||
else
|
||||
src.opened = 0
|
||||
panel_open = 0
|
||||
overlays -= image(icon, "npanel_open")
|
||||
user << "You screw the control panel of [src] back on."
|
||||
playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
|
||||
else
|
||||
if (src.opened == 0)
|
||||
user << "The [src] emits a buzzing noise, the panel staying locked in."
|
||||
if (src.opened == 1)
|
||||
src.opened = 0
|
||||
if (panel_open == 0)
|
||||
user << "\The [src] emits a buzzing noise, the panel staying locked in."
|
||||
if (panel_open == 1)
|
||||
panel_open = 0
|
||||
overlays -= image(icon, "npanel_open")
|
||||
user << "You screw the control panel of [src] back on."
|
||||
user << "You screw the control panel of \the [src] back on."
|
||||
playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
|
||||
flick("nuclearbombc", src)
|
||||
return
|
||||
|
||||
return
|
||||
if (istype(O, /obj/item/weapon/wirecutters) || istype(O, /obj/item/device/multitool))
|
||||
if (src.opened == 1)
|
||||
nukehack_win(user)
|
||||
return
|
||||
if (panel_open && (istype(O, /obj/item/device/multitool) || istype(O, /obj/item/weapon/wirecutters)))
|
||||
return attack_hand(user)
|
||||
|
||||
if (src.extended)
|
||||
if (istype(O, /obj/item/weapon/disk/nuclear))
|
||||
@@ -92,13 +76,12 @@ var/bomb_set
|
||||
O.loc = src
|
||||
src.auth = O
|
||||
src.add_fingerprint(user)
|
||||
return
|
||||
return attack_hand(user)
|
||||
|
||||
if (src.anchored)
|
||||
switch(removal_stage)
|
||||
if(0)
|
||||
if(istype(O,/obj/item/weapon/weldingtool))
|
||||
|
||||
var/obj/item/weapon/weldingtool/WT = O
|
||||
if(!WT.isOn()) return
|
||||
if (WT.get_fuel() < 5) // uses up 5 fuel.
|
||||
@@ -164,65 +147,67 @@ var/bomb_set
|
||||
return
|
||||
..()
|
||||
|
||||
/obj/machinery/nuclearbomb/attack_hand(mob/user as mob)
|
||||
if (src.extended)
|
||||
if (!ishuman(user))
|
||||
usr << "<span class='warning'>You don't have the dexterity to do this!</span>"
|
||||
return 1
|
||||
/obj/machinery/nuclearbomb/attack_ghost(mob/user as mob)
|
||||
attack_hand(user)
|
||||
|
||||
user.set_machine(src)
|
||||
var/dat = text("<TT><B>Nuclear Fission Explosive</B><BR>\nAuth. Disk: <A href='?src=\ref[];auth=1'>[]</A><HR>", src, (src.auth ? "++++++++++" : "----------"))
|
||||
if (src.auth)
|
||||
if (src.yes_code)
|
||||
dat += text("\n<B>Status</B>: []-[]<BR>\n<B>Timer</B>: []<BR>\n<BR>\nTimer: [] <A href='?src=\ref[];timer=1'>Toggle</A><BR>\nTime: <A href='?src=\ref[];time=-10'>-</A> <A href='?src=\ref[];time=-1'>-</A> [] <A href='?src=\ref[];time=1'>+</A> <A href='?src=\ref[];time=10'>+</A><BR>\n<BR>\nSafety: [] <A href='?src=\ref[];safety=1'>Toggle</A><BR>\nAnchor: [] <A href='?src=\ref[];anchor=1'>Toggle</A><BR>\n", (src.timing ? "Func/Set" : "Functional"), (src.safety ? "Safe" : "Engaged"), src.timeleft, (src.timing ? "On" : "Off"), src, src, src, src.timeleft, src, src, (src.safety ? "On" : "Off"), src, (src.anchored ? "Engaged" : "Off"), src)
|
||||
else
|
||||
dat += text("\n<B>Status</B>: Auth. S2-[]<BR>\n<B>Timer</B>: []<BR>\n<BR>\nTimer: [] Toggle<BR>\nTime: - - [] + +<BR>\n<BR>\n[] Safety: Toggle<BR>\nAnchor: [] Toggle<BR>\n", (src.safety ? "Safe" : "Engaged"), src.timeleft, (src.timing ? "On" : "Off"), src.timeleft, (src.safety ? "On" : "Off"), (src.anchored ? "Engaged" : "Off"))
|
||||
/obj/machinery/nuclearbomb/attack_hand(mob/user as mob)
|
||||
if (extended)
|
||||
if (panel_open)
|
||||
wires.Interact(user)
|
||||
else
|
||||
if (src.timing)
|
||||
dat += text("\n<B>Status</B>: Set-[]<BR>\n<B>Timer</B>: []<BR>\n<BR>\nTimer: [] Toggle<BR>\nTime: - - [] + +<BR>\n<BR>\nSafety: [] Toggle<BR>\nAnchor: [] Toggle<BR>\n", (src.safety ? "Safe" : "Engaged"), src.timeleft, (src.timing ? "On" : "Off"), src.timeleft, (src.safety ? "On" : "Off"), (src.anchored ? "Engaged" : "Off"))
|
||||
else
|
||||
dat += text("\n<B>Status</B>: Auth. S1-[]<BR>\n<B>Timer</B>: []<BR>\n<BR>\nTimer: [] Toggle<BR>\nTime: - - [] + +<BR>\n<BR>\nSafety: [] Toggle<BR>\nAnchor: [] Toggle<BR>\n", (src.safety ? "Safe" : "Engaged"), src.timeleft, (src.timing ? "On" : "Off"), src.timeleft, (src.safety ? "On" : "Off"), (src.anchored ? "Engaged" : "Off"))
|
||||
var/message = "AUTH"
|
||||
if (src.auth)
|
||||
message = text("[]", src.code)
|
||||
if (src.yes_code)
|
||||
message = "*****"
|
||||
dat += text("<HR>\n>[]<BR>\n<A href='?src=\ref[];type=1'>1</A>-<A href='?src=\ref[];type=2'>2</A>-<A href='?src=\ref[];type=3'>3</A><BR>\n<A href='?src=\ref[];type=4'>4</A>-<A href='?src=\ref[];type=5'>5</A>-<A href='?src=\ref[];type=6'>6</A><BR>\n<A href='?src=\ref[];type=7'>7</A>-<A href='?src=\ref[];type=8'>8</A>-<A href='?src=\ref[];type=9'>9</A><BR>\n<A href='?src=\ref[];type=R'>R</A>-<A href='?src=\ref[];type=0'>0</A>-<A href='?src=\ref[];type=E'>E</A><BR>\n</TT>", message, src, src, src, src, src, src, src, src, src, src, src, src)
|
||||
user << browse(dat, "window=nuclearbomb;size=300x400")
|
||||
onclose(user, "nuclearbomb")
|
||||
else if (src.deployable)
|
||||
ui_interact(user)
|
||||
else if (deployable)
|
||||
if(removal_stage < 5)
|
||||
src.anchored = 1
|
||||
visible_message("<span class='warning'>With a steely snap, bolts slide out of [src] and anchor it to the flooring!</span>")
|
||||
else
|
||||
visible_message("<span class='warning'>\The [src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.</span>")
|
||||
extended = 1
|
||||
if(!src.lighthack)
|
||||
flick("nuclearbombc", src)
|
||||
src.icon_state = "nuclearbomb1"
|
||||
src.extended = 1
|
||||
update_icon()
|
||||
return
|
||||
|
||||
obj/machinery/nuclearbomb/proc/nukehack_win(mob/user as mob)
|
||||
var/dat as text
|
||||
dat += "<TT><B>Nuclear Fission Explosive</B><BR>\nNuclear Device Wires:</A><HR>"
|
||||
for(var/wire in src.wires)
|
||||
dat += text("[wire] Wire: <A href='?src=\ref[src];wire=[wire];act=wire'>[src.wires[wire] ? "Mend" : "Cut"]</A> <A href='?src=\ref[src];wire=[wire];act=pulse'>Pulse</A><BR>")
|
||||
dat += text("<HR>The device is [src.timing ? "shaking!" : "still"]<BR>")
|
||||
dat += text("The device is [src.safety ? "quiet" : "whirring"].<BR>")
|
||||
dat += text("The lights are [src.lighthack ? "static" : "functional"].<BR>")
|
||||
user << browse("<HTML><HEAD><TITLE>Bomb Defusion</TITLE></HEAD><BODY>[dat]</BODY></HTML>","window=nukebomb_hack")
|
||||
onclose(user, "nukebomb_hack")
|
||||
/obj/machinery/nuclearbomb/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
|
||||
var/data[0]
|
||||
data["hacking"] = 0
|
||||
data["auth"] = is_auth(user)
|
||||
if (is_auth(user))
|
||||
if (yes_code)
|
||||
data["authstatus"] = timing ? "Functional/Set" : "Functional"
|
||||
else
|
||||
data["authstatus"] = "Auth. S2"
|
||||
else
|
||||
if (timing)
|
||||
data["authstatus"] = "Set"
|
||||
else
|
||||
data["authstatus"] = "Auth. S1"
|
||||
data["safe"] = safety ? "Safe" : "Engaged"
|
||||
data["time"] = timeleft
|
||||
data["timer"] = timing
|
||||
data["safety"] = safety
|
||||
data["anchored"] = anchored
|
||||
data["yescode"] = yes_code
|
||||
data["message"] = "AUTH"
|
||||
if (is_auth(user))
|
||||
data["message"] = code
|
||||
if (yes_code)
|
||||
data["message"] = "*****"
|
||||
|
||||
/obj/machinery/nuclearbomb/verb/make_deployable()
|
||||
ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
|
||||
if (!ui)
|
||||
ui = new(user, src, ui_key, "nuclear_bomb.tmpl", "Nuke Control Panel", 300, 510)
|
||||
ui.set_initial_data(data)
|
||||
ui.open()
|
||||
ui.set_auto_update(1)
|
||||
|
||||
/obj/machinery/nuclearbomb/verb/toggle_deployable()
|
||||
set category = "Object"
|
||||
set name = "Make Deployable"
|
||||
set name = "Toggle Deployable"
|
||||
set src in oview(1)
|
||||
|
||||
if (!usr.canmove || usr.stat || usr.restrained())
|
||||
if(usr.incapacitated())
|
||||
return
|
||||
if (!ishuman(usr))
|
||||
usr << "<span class='warning'>You don't have the dexterity to do this!</span>"
|
||||
return 1
|
||||
|
||||
if (src.deployable)
|
||||
usr << "<span class='warning'>You close several panels to make [src] undeployable.</span>"
|
||||
@@ -232,147 +217,127 @@ obj/machinery/nuclearbomb/proc/nukehack_win(mob/user as mob)
|
||||
src.deployable = 1
|
||||
return
|
||||
|
||||
/obj/machinery/nuclearbomb/proc/is_auth(var/mob/user)
|
||||
if(auth)
|
||||
return 1
|
||||
if(user.can_admin_interact())
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/obj/machinery/nuclearbomb/Topic(href, href_list)
|
||||
..()
|
||||
if (!usr.canmove || usr.stat || usr.restrained())
|
||||
return
|
||||
if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))))
|
||||
usr.set_machine(src)
|
||||
if(href_list["act"])
|
||||
var/temp_wire = href_list["wire"]
|
||||
if(href_list["act"] == "pulse")
|
||||
if (!istype(usr.get_active_hand(), /obj/item/device/multitool))
|
||||
usr << "You need a multitool!"
|
||||
else
|
||||
if(src.wires[temp_wire])
|
||||
usr << "You can't pulse a cut wire."
|
||||
else
|
||||
if(src.light_wire == temp_wire)
|
||||
src.lighthack = !src.lighthack
|
||||
spawn(100) src.lighthack = !src.lighthack
|
||||
if(src.timing_wire == temp_wire)
|
||||
if(src.timing)
|
||||
explode()
|
||||
if(src.safety_wire == temp_wire)
|
||||
src.safety = !src.safety
|
||||
spawn(100) src.safety = !src.safety
|
||||
if(src.safety == 1)
|
||||
visible_message("<span class='notice'>The [src] quiets down.</span>")
|
||||
if(!src.lighthack)
|
||||
if (src.icon_state == "nuclearbomb2")
|
||||
src.icon_state = "nuclearbomb1"
|
||||
else
|
||||
visible_message("<span class='notice'>The [src] emits a quiet whirling noise!</span>")
|
||||
if(href_list["act"] == "wire")
|
||||
if (!istype(usr.get_active_hand(), /obj/item/weapon/wirecutters))
|
||||
usr << "You need wirecutters!"
|
||||
else
|
||||
wires[temp_wire] = !wires[temp_wire]
|
||||
if(src.safety_wire == temp_wire)
|
||||
if(src.timing)
|
||||
explode()
|
||||
if(src.timing_wire == temp_wire)
|
||||
if(!src.lighthack)
|
||||
if (src.icon_state == "nuclearbomb2")
|
||||
src.icon_state = "nuclearbomb1"
|
||||
src.timing = 0
|
||||
bomb_set = 0
|
||||
if(src.light_wire == temp_wire)
|
||||
src.lighthack = !src.lighthack
|
||||
if(..())
|
||||
return 1
|
||||
|
||||
if (href_list["auth"])
|
||||
if (src.auth)
|
||||
src.auth.loc = src.loc
|
||||
src.yes_code = 0
|
||||
src.auth = null
|
||||
if (href_list["auth"])
|
||||
if (auth)
|
||||
auth.loc = loc
|
||||
yes_code = 0
|
||||
auth = null
|
||||
else
|
||||
var/obj/item/I = usr.get_active_hand()
|
||||
if (istype(I, /obj/item/weapon/disk/nuclear))
|
||||
usr.drop_item()
|
||||
I.loc = src
|
||||
auth = I
|
||||
if (is_auth(usr))
|
||||
if (href_list["type"])
|
||||
if (href_list["type"] == "E")
|
||||
if (code == r_code)
|
||||
yes_code = 1
|
||||
code = null
|
||||
else
|
||||
code = "ERROR"
|
||||
else
|
||||
var/obj/item/I = usr.get_active_hand()
|
||||
if (istype(I, /obj/item/weapon/disk/nuclear))
|
||||
usr.drop_item()
|
||||
I.loc = src
|
||||
src.auth = I
|
||||
if (src.auth)
|
||||
if (href_list["type"])
|
||||
if (href_list["type"] == "E")
|
||||
if (src.code == src.r_code)
|
||||
src.yes_code = 1
|
||||
src.code = null
|
||||
else
|
||||
src.code = "ERROR"
|
||||
if (href_list["type"] == "R")
|
||||
yes_code = 0
|
||||
code = null
|
||||
else
|
||||
if (href_list["type"] == "R")
|
||||
src.yes_code = 0
|
||||
src.code = null
|
||||
lastentered = text("[]", href_list["type"])
|
||||
if (text2num(lastentered) == null)
|
||||
var/turf/LOC = get_turf(usr)
|
||||
message_admins("[key_name_admin(usr)] tried to exploit a nuclear bomb by entering non-numerical codes: <a href='?_src_=vars;Vars=\ref[src]'>[lastentered]</a>! ([LOC ? "<a href='?_src_=holder;adminplayerobservecoodjump=1;X=[LOC.x];Y=[LOC.y];Z=[LOC.z]'>JMP</a>" : "null"])", 0)
|
||||
log_admin("EXPLOIT: [key_name(usr)] tried to exploit a nuclear bomb by entering non-numerical codes: [lastentered]!")
|
||||
else
|
||||
src.code += text("[]", href_list["type"])
|
||||
if (length(src.code) > 5)
|
||||
src.code = "ERROR"
|
||||
if (src.yes_code)
|
||||
if (href_list["time"])
|
||||
var/time = text2num(href_list["time"])
|
||||
src.timeleft += time
|
||||
src.timeleft = min(max(round(src.timeleft), 60), 600)
|
||||
if (href_list["timer"])
|
||||
if (src.timing == -1.0)
|
||||
return
|
||||
if (src.safety)
|
||||
usr << "<span class='warning'>The safety is still on.</span>"
|
||||
return
|
||||
src.timing = !( src.timing )
|
||||
if (src.timing)
|
||||
if(!src.lighthack)
|
||||
src.icon_state = "nuclearbomb2"
|
||||
if(!src.safety)
|
||||
bomb_set = 1//There can still be issues with this reseting when there are multiple bombs. Not a big deal tho for Nuke/N
|
||||
else
|
||||
bomb_set = 0
|
||||
else
|
||||
bomb_set = 0
|
||||
if(!src.lighthack)
|
||||
src.icon_state = "nuclearbomb1"
|
||||
if (href_list["safety"])
|
||||
src.safety = !( src.safety )
|
||||
if(safety)
|
||||
src.timing = 0
|
||||
bomb_set = 0
|
||||
if (href_list["anchor"])
|
||||
code += lastentered
|
||||
if (length(code) > 5)
|
||||
code = "ERROR"
|
||||
if (yes_code)
|
||||
if (href_list["time"])
|
||||
var/time = text2num(href_list["time"])
|
||||
timeleft += time
|
||||
timeleft = Clamp(timeleft, 120, 600)
|
||||
if (href_list["timer"])
|
||||
if (timing == -1)
|
||||
nanomanager.update_uis(src)
|
||||
return
|
||||
if (!anchored)
|
||||
usr << "<span class='warning'>\The [src] needs to be anchored.</span>"
|
||||
nanomanager.update_uis(src)
|
||||
return
|
||||
if (safety)
|
||||
usr << "<span class='warning'>The safety is still on.</span>"
|
||||
nanomanager.update_uis(src)
|
||||
return
|
||||
if (wires.IsIndexCut(NUCLEARBOMB_WIRE_TIMING))
|
||||
usr << "<span class='warning'>Nothing happens, something might be wrong with the wiring.</span>"
|
||||
nanomanager.update_uis(src)
|
||||
return
|
||||
|
||||
if(removal_stage == 5)
|
||||
src.anchored = 0
|
||||
visible_message("<span class='warning'>\The [src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.</span>")
|
||||
return
|
||||
if (!timing && !safety)
|
||||
timing = 1
|
||||
log_and_message_admins_with_location("engaged a nuclear bomb", x, y, ,z)
|
||||
bomb_set++ //There can still be issues with this resetting when there are multiple bombs. Not a big deal though for Nuke/N
|
||||
update_icon()
|
||||
else
|
||||
secure_device()
|
||||
if (href_list["safety"])
|
||||
if (wires.IsIndexCut(NUCLEARBOMB_WIRE_SAFETY))
|
||||
usr << "<span class='warning'>Nothing happens, something might be wrong with the wiring.</span>"
|
||||
nanomanager.update_uis(src)
|
||||
return
|
||||
safety = !safety
|
||||
if(safety)
|
||||
secure_device()
|
||||
if (href_list["anchor"])
|
||||
if(removal_stage == 5)
|
||||
anchored = 0
|
||||
visible_message("<span class='warning'>\The [src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.</span>")
|
||||
nanomanager.update_uis(src)
|
||||
return
|
||||
|
||||
src.anchored = !( src.anchored )
|
||||
if(src.anchored)
|
||||
if(!isinspace())
|
||||
anchored = !anchored
|
||||
if(anchored)
|
||||
visible_message("<span class='warning'>With a steely snap, bolts slide out of [src] and anchor it to the flooring.</span>")
|
||||
else
|
||||
secure_device()
|
||||
visible_message("<span class='warning'>The anchoring bolts slide back into the depths of [src].</span>")
|
||||
else
|
||||
usr << "<span class='warning'>There is nothing to anchor to!</span>"
|
||||
|
||||
src.add_fingerprint(usr)
|
||||
for(var/mob/M in viewers(1, src))
|
||||
if ((M.client && M.machine == src))
|
||||
src.attack_hand(M)
|
||||
else
|
||||
usr << browse(null, "window=nuclearbomb")
|
||||
nanomanager.update_uis(src)
|
||||
|
||||
/obj/machinery/nuclearbomb/proc/secure_device()
|
||||
if(timing <= 0)
|
||||
return
|
||||
return
|
||||
|
||||
bomb_set--
|
||||
timing = 0
|
||||
timeleft = Clamp(timeleft, 120, 600)
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/nuclearbomb/ex_act(severity)
|
||||
return
|
||||
|
||||
|
||||
#define NUKERANGE 80
|
||||
/obj/machinery/nuclearbomb/proc/explode()
|
||||
if (src.safety)
|
||||
src.timing = 0
|
||||
timing = 0
|
||||
return
|
||||
src.timing = -1.0
|
||||
src.timing = -1
|
||||
src.yes_code = 0
|
||||
src.safety = 1
|
||||
if(!src.lighthack)
|
||||
src.icon_state = "nuclearbomb3"
|
||||
update_icon()
|
||||
playsound(src,'sound/machines/Alarm.ogg',100,0,5)
|
||||
if (ticker && ticker.mode)
|
||||
ticker.mode.explosion_in_progress = 1
|
||||
@@ -395,24 +360,49 @@ obj/machinery/nuclearbomb/proc/nukehack_win(mob/user as mob)
|
||||
ticker.station_explosion_cinematic(off_station,null)
|
||||
if(ticker.mode)
|
||||
ticker.mode.explosion_in_progress = 0
|
||||
world << "<B>The station was destoyed by the nuclear blast!</B>"
|
||||
if(off_station == 1)
|
||||
world << "<b>A nuclear device was set off, but the explosion was out of reach of the station!</b>"
|
||||
else if(off_station == 2)
|
||||
world << "<b>A nuclear device was set off, but the device was not on the station!</b>"
|
||||
else
|
||||
world << "<b>The station was destoyed by the nuclear blast!</b>"
|
||||
|
||||
ticker.mode.station_was_nuked = (off_station<2) //offstation==1 is a draw. the station becomes irradiated and needs to be evacuated.
|
||||
//kinda shit but I couldn't get permission to do what I wanted to do.
|
||||
|
||||
if(!ticker.mode.check_finished())//If the mode does not deal with the nuke going off so just reboot because everyone is stuck as is
|
||||
world << "<B>Resetting in 30 seconds!</B>"
|
||||
|
||||
feedback_set_details("end_error","nuke - unhandled ending")
|
||||
|
||||
if(blackbox)
|
||||
blackbox.save_all_data_to_sql()
|
||||
sleep(300)
|
||||
log_game("Rebooting due to nuclear detonation")
|
||||
world.Reboot()
|
||||
universe_has_ended = 1
|
||||
return
|
||||
return
|
||||
|
||||
/obj/machinery/nuclearbomb/update_icon()
|
||||
if(lighthack)
|
||||
icon_state = "nuclearbomb0"
|
||||
return
|
||||
|
||||
else if(timing == -1)
|
||||
icon_state = "nuclearbomb3"
|
||||
else if(timing)
|
||||
icon_state = "nuclearbomb2"
|
||||
else if(extended)
|
||||
icon_state = "nuclearbomb1"
|
||||
else
|
||||
icon_state = "nuclearbomb0"
|
||||
/*
|
||||
if(!N.lighthack)
|
||||
if (N.icon_state == "nuclearbomb2")
|
||||
N.icon_state = "nuclearbomb1"
|
||||
*/
|
||||
|
||||
//====The nuclear authentication disc====
|
||||
/obj/item/weapon/disk/nuclear
|
||||
name = "nuclear authentication disk"
|
||||
desc = "Better keep this safe."
|
||||
icon = 'icons/obj/items.dmi'
|
||||
icon_state = "nucleardisk"
|
||||
item_state = "card-id"
|
||||
w_class = 1.0
|
||||
|
||||
/obj/item/weapon/disk/nuclear/New()
|
||||
..()
|
||||
nuke_disks |= src
|
||||
@@ -426,7 +416,7 @@ obj/machinery/nuclearbomb/proc/nukehack_win(mob/user as mob)
|
||||
log_and_message_admins_with_location("[src], the last authentication disk, has been destroyed. Spawning [D] at ([D.x], [D.y], [D.z]).", T.x, T.y, T.z)
|
||||
else
|
||||
log_and_message_admins("[src], the last authentication disk, has been destroyed. Failed to respawn disc!")
|
||||
..()
|
||||
return ..()
|
||||
|
||||
/obj/item/weapon/disk/nuclear/touch_map_edge()
|
||||
qdel(src)
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
var/one_time_use = 0 //Used for one-time-use teleport cards (such as clown planet coordinates.)
|
||||
//Setting this to 1 will set src.locked to null after a player enters the portal and will not allow hand-teles to open portals to that location.
|
||||
|
||||
/* Ghosts can't use this */
|
||||
/obj/machinery/computer/teleporter/attack_ghost(user as mob)
|
||||
return 1
|
||||
|
||||
/obj/machinery/computer/teleporter/New()
|
||||
src.id = "[rand(1000, 9999)]"
|
||||
..()
|
||||
|
||||
@@ -22,6 +22,19 @@ var/global/list/image/splatter_cache=list()
|
||||
var/amount = 5
|
||||
var/drytime
|
||||
|
||||
/obj/effect/decal/cleanable/blood/reveal_blood()
|
||||
if(!fluorescent)
|
||||
fluorescent = 1
|
||||
basecolor = COLOR_LUMINOL
|
||||
update_icon()
|
||||
|
||||
/obj/effect/decal/cleanable/blood/clean_blood()
|
||||
fluorescent = 0
|
||||
if(invisibility != 100)
|
||||
invisibility = 100
|
||||
amount = 0
|
||||
processing_objects -= src
|
||||
|
||||
/obj/effect/decal/cleanable/blood/Destroy()
|
||||
for(var/datum/disease/D in viruses)
|
||||
D.cure(0)
|
||||
|
||||
@@ -29,6 +29,13 @@ var/global/list/image/fluidtrack_cache=list()
|
||||
src.basecolor=_color
|
||||
src.wet=_wet
|
||||
|
||||
/obj/effect/decal/cleanable/blood/tracks/reveal_blood()
|
||||
if(!fluorescent)
|
||||
if(stack && stack.len)
|
||||
for(var/datum/fluidtrack/track in stack)
|
||||
track.basecolor = COLOR_LUMINOL
|
||||
..()
|
||||
|
||||
// Footprints, tire trails...
|
||||
/obj/effect/decal/cleanable/blood/tracks
|
||||
amount = 0
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/obj/effect/decal/cleanable
|
||||
var/list/random_icon_states = list()
|
||||
|
||||
/obj/effect/decal/cleanable/clean_blood()
|
||||
qdel(src)
|
||||
|
||||
/obj/effect/decal/cleanable/New()
|
||||
if (random_icon_states && length(src.random_icon_states) > 0)
|
||||
src.icon_state = pick(src.random_icon_states)
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
//It should be used purely for appearance. For gameplay effects caused by items covering body parts, use body_parts_covered.
|
||||
var/flags_inv = 0
|
||||
var/body_parts_covered = 0 //see setup.dm for appropriate bit flags
|
||||
|
||||
|
||||
var/item_flags = 0 //Miscellaneous flags pertaining to equippable objects.
|
||||
|
||||
|
||||
//var/heat_transfer_coefficient = 1 //0 prevents all transfers, 1 is invisible
|
||||
var/gas_transfer_coefficient = 1 // for leaking gas from turf to mask and vice-versa (for masks right now, but at some point, i'd like to include space helmets)
|
||||
var/permeability_coefficient = 1 // for chemicals/diseases
|
||||
@@ -497,6 +497,12 @@ var/list/global/slot_flags_enumeration = list(
|
||||
var/obj/item/clothing/gloves/G = src
|
||||
G.transfer_blood = 0
|
||||
|
||||
/obj/item/reveal_blood()
|
||||
if(was_bloodied && !fluorescent)
|
||||
fluorescent = 1
|
||||
blood_color = COLOR_LUMINOL
|
||||
blood_overlay.color = COLOR_LUMINOL
|
||||
update_icon()
|
||||
|
||||
/obj/item/add_blood(mob/living/carbon/human/M as mob)
|
||||
if (!..())
|
||||
|
||||
100
code/game/objects/items/devices/hacktool.dm
Normal file
100
code/game/objects/items/devices/hacktool.dm
Normal file
@@ -0,0 +1,100 @@
|
||||
/obj/item/device/multitool/hacktool
|
||||
var/is_hacking = 0
|
||||
var/max_known_targets
|
||||
|
||||
var/in_hack_mode = 0
|
||||
var/list/known_targets
|
||||
var/list/supported_types
|
||||
var/datum/topic_state/default/must_hack/hack_state
|
||||
|
||||
/obj/item/device/multitool/hacktool/New()
|
||||
..()
|
||||
known_targets = list()
|
||||
max_known_targets = 5 + rand(1,3)
|
||||
supported_types = list(/obj/machinery/door/airlock)
|
||||
hack_state = new(src)
|
||||
|
||||
/obj/item/device/multitool/hacktool/Destroy()
|
||||
for(var/T in known_targets)
|
||||
var/atom/target = T
|
||||
target.unregister(OBSERVER_EVENT_DESTROY, src)
|
||||
known_targets.Cut()
|
||||
qdel(hack_state)
|
||||
hack_state = null
|
||||
return ..()
|
||||
|
||||
/obj/item/device/multitool/hacktool/attackby(var/obj/W, var/mob/user)
|
||||
if(isscrewdriver(W))
|
||||
in_hack_mode = !in_hack_mode
|
||||
playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
|
||||
else
|
||||
..()
|
||||
|
||||
/obj/item/device/multitool/hacktool/resolve_attackby(atom/A, mob/user)
|
||||
sanity_check()
|
||||
|
||||
if(!in_hack_mode)
|
||||
return ..()
|
||||
|
||||
if(!attempt_hack(user, A))
|
||||
return 0
|
||||
|
||||
A.ui_interact(user, state = hack_state)
|
||||
return 1
|
||||
|
||||
/obj/item/device/multitool/hacktool/proc/attempt_hack(var/mob/user, var/atom/target)
|
||||
if(is_hacking)
|
||||
user << "<span class='warning'>You are already hacking!</span>"
|
||||
return 0
|
||||
if(!is_type_in_list(target, supported_types))
|
||||
user << "\icon[src] <span class='warning'>Unable to hack this target!</span>"
|
||||
return 0
|
||||
var/found = known_targets.Find(target)
|
||||
if(found)
|
||||
known_targets.Swap(1, found) // Move the last hacked item first
|
||||
return 1
|
||||
|
||||
user << "<span class='notice'>You begin hacking \the [target]...</span>"
|
||||
is_hacking = 1
|
||||
// On average hackin takes ~30 seconds. Fairly small random span to avoid people simply aborting and trying again
|
||||
var/hack_result = do_after(user, (20 SECONDS + rand(0, 10 SECONDS) + rand(0, 10 SECONDS)))
|
||||
is_hacking = 0
|
||||
|
||||
if(hack_result && in_hack_mode)
|
||||
user << "<span class='notice'>Your hacking attempt was succesful!</span>"
|
||||
playsound(src.loc, 'sound/piano/A#6.ogg', 75)
|
||||
else
|
||||
user << "<span class='warning'>Your hacking attempt failed!</span>"
|
||||
return 0
|
||||
|
||||
known_targets.Insert(1, target) // Insert the newly hacked target first,
|
||||
target.register(OBSERVER_EVENT_DESTROY, src, /obj/item/device/multitool/hacktool/proc/on_target_destroy)
|
||||
return 1
|
||||
|
||||
/obj/item/device/multitool/hacktool/proc/sanity_check()
|
||||
if(max_known_targets < 1) max_known_targets = 1
|
||||
// Cut away the oldest items if the capacity has been reached
|
||||
if(known_targets.len > max_known_targets)
|
||||
for(var/i = (max_known_targets + 1) to known_targets.len)
|
||||
var/atom/A = known_targets[i]
|
||||
A.unregister(OBSERVER_EVENT_DESTROY, src)
|
||||
known_targets.Cut(max_known_targets + 1)
|
||||
|
||||
/obj/item/device/multitool/hacktool/proc/on_target_destroy(var/target)
|
||||
known_targets -= target
|
||||
|
||||
/datum/topic_state/default/must_hack
|
||||
var/obj/item/device/multitool/hacktool/hacktool
|
||||
|
||||
/datum/topic_state/default/must_hack/New(var/hacktool)
|
||||
src.hacktool = hacktool
|
||||
..()
|
||||
|
||||
/datum/topic_state/default/must_hack/Destroy()
|
||||
hacktool = null
|
||||
return ..()
|
||||
|
||||
/datum/topic_state/default/must_hack/can_use_topic(var/src_object, var/mob/user)
|
||||
if(!hacktool || !hacktool.in_hack_mode || !(src_object in hacktool.known_targets))
|
||||
return STATUS_CLOSE
|
||||
return ..()
|
||||
@@ -20,4 +20,4 @@
|
||||
|
||||
origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1)
|
||||
var/obj/machinery/telecomms/buffer // simple machine buffer for device linkage
|
||||
var/obj/machinery/clonepod/connecting //same for cryopod linkage
|
||||
var/obj/machinery/clonepod/connecting //same for cryopod linkage
|
||||
|
||||
@@ -83,9 +83,6 @@ var/global/list/default_medbay_channels = list(
|
||||
for (var/ch_name in channels)
|
||||
secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT)
|
||||
|
||||
/obj/item/device/radio/attack_ghost(mob/user)
|
||||
return ui_interact(user)
|
||||
|
||||
/obj/item/device/radio/attack_self(mob/user as mob)
|
||||
user.set_machine(src)
|
||||
interact(user)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
default_material = "wood"
|
||||
force_divisor = 1.1 // 22 when wielded with weight 20 (steel)
|
||||
unwielded_force_divisor = 0.7 // 15 when unwielded based on above.
|
||||
slot_flags = SLOT_BACK
|
||||
|
||||
//Predefined materials go here.
|
||||
/obj/item/weapon/material/twohanded/baseballbat/metal/New(var/newloc)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
name = "jetpack (empty)"
|
||||
desc = "A tank of compressed gas for use as propulsion in zero-gravity areas. Use with caution."
|
||||
icon_state = "jetpack"
|
||||
gauge_icon = null
|
||||
w_class = 4.0
|
||||
item_state = "jetpack"
|
||||
distribute_pressure = ONE_ATMOSPHERE*O2STANDARD
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
name = "phoron tank"
|
||||
desc = "Contains dangerous phoron. Do not inhale. Warning: extremely flammable."
|
||||
icon_state = "phoron"
|
||||
gauge_icon = null
|
||||
flags = CONDUCT
|
||||
slot_flags = null //they have no straps!
|
||||
|
||||
@@ -114,6 +115,8 @@
|
||||
name = "emergency oxygen tank"
|
||||
desc = "Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it."
|
||||
icon_state = "emergency"
|
||||
gauge_icon = "indicator_emergency"
|
||||
gauge_cap = 4
|
||||
flags = CONDUCT
|
||||
slot_flags = SLOT_BELT
|
||||
w_class = 2.0
|
||||
@@ -142,12 +145,15 @@
|
||||
/obj/item/weapon/tank/emergency_oxygen/double
|
||||
name = "double emergency oxygen tank"
|
||||
icon_state = "emergency_double"
|
||||
gauge_icon = "indicator_emergency_double"
|
||||
volume = 10
|
||||
|
||||
/obj/item/weapon/tank/emergency_nitrogen
|
||||
name = "emergency nitrogen tank"
|
||||
desc = "An emergency air tank hastily painted red and issued to Vox crewmembers."
|
||||
icon_state = "emergency_nitro"
|
||||
gauge_icon = "indicator_emergency"
|
||||
gauge_cap = 4
|
||||
flags = CONDUCT
|
||||
slot_flags = SLOT_BELT
|
||||
w_class = 2.0
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
#define TANK_MAX_RELEASE_PRESSURE (3*ONE_ATMOSPHERE)
|
||||
#define TANK_DEFAULT_RELEASE_PRESSURE 24
|
||||
#define TANK_IDEAL_PRESSURE 1015 //Arbitrary.
|
||||
|
||||
var/list/global/tank_gauge_cache = list()
|
||||
|
||||
/obj/item/weapon/tank
|
||||
name = "tank"
|
||||
icon = 'icons/obj/tank.dmi'
|
||||
|
||||
var/gauge_icon = "indicator_tank"
|
||||
var/last_gauge_pressure
|
||||
var/gauge_cap = 6
|
||||
|
||||
flags = CONDUCT
|
||||
slot_flags = SLOT_BACK
|
||||
w_class = 3
|
||||
@@ -31,8 +39,8 @@
|
||||
src.air_contents = new /datum/gas_mixture()
|
||||
src.air_contents.volume = volume //liters
|
||||
src.air_contents.temperature = T20C
|
||||
|
||||
processing_objects.Add(src)
|
||||
update_gauge()
|
||||
return
|
||||
|
||||
/obj/item/weapon/tank/Destroy()
|
||||
@@ -220,8 +228,28 @@
|
||||
/obj/item/weapon/tank/process()
|
||||
//Allow for reactions
|
||||
air_contents.react() //cooking up air tanks - add phoron and oxygen, then heat above PHORON_MINIMUM_BURN_TEMPERATURE
|
||||
if(gauge_icon)
|
||||
update_gauge()
|
||||
check_status()
|
||||
|
||||
/obj/item/weapon/tank/proc/update_gauge()
|
||||
var/gauge_pressure = 0
|
||||
if(air_contents)
|
||||
gauge_pressure = air_contents.return_pressure()
|
||||
if(gauge_pressure > TANK_IDEAL_PRESSURE)
|
||||
gauge_pressure = -1
|
||||
else
|
||||
gauge_pressure = round((gauge_pressure/TANK_IDEAL_PRESSURE)*gauge_cap)
|
||||
|
||||
if(gauge_pressure == last_gauge_pressure)
|
||||
return
|
||||
|
||||
last_gauge_pressure = gauge_pressure
|
||||
overlays.Cut()
|
||||
var/indicator = "[gauge_icon][(gauge_pressure == -1) ? "overload" : gauge_pressure]"
|
||||
if(!tank_gauge_cache[indicator])
|
||||
tank_gauge_cache[indicator] = image(icon, indicator)
|
||||
overlays += tank_gauge_cache[indicator]
|
||||
|
||||
/obj/item/weapon/tank/proc/check_status()
|
||||
//Handle exploding, leaking, and rupturing of the tank
|
||||
|
||||
@@ -117,6 +117,9 @@
|
||||
if(!ai_in_use && !is_in_use)
|
||||
in_use = 0
|
||||
|
||||
/obj/attack_ghost(mob/user)
|
||||
ui_interact(user)
|
||||
|
||||
/obj/proc/interact(mob/user)
|
||||
return
|
||||
|
||||
|
||||
@@ -66,11 +66,12 @@
|
||||
src.MouseDrop_T(W:affecting, user) //act like they were dragged onto the closet
|
||||
user.drop_item()
|
||||
if (W) W.forceMove(src.loc)
|
||||
else if(istype(W, /obj/item/weapon/card/id))
|
||||
else if(W.GetID())
|
||||
var/obj/item/weapon/card/id/I = W.GetID()
|
||||
|
||||
if(src.broken)
|
||||
user << "<span class='warning'>It appears to be broken.</span>"
|
||||
return
|
||||
var/obj/item/weapon/card/id/I = W
|
||||
if(!I || !I.registered_name) return
|
||||
if(src.allowed(user) || !src.registered_name || (istype(I) && (src.registered_name == I.registered_name)))
|
||||
//they can open all lockers, or nobody owns this, or they own this locker
|
||||
|
||||
@@ -258,7 +258,6 @@
|
||||
new /obj/item/clothing/shoes/laceup(src)
|
||||
new /obj/item/weapon/storage/box/evidence(src)
|
||||
new /obj/item/device/radio/headset/headset_sec(src)
|
||||
new /obj/item/device/detective_scanner(src)
|
||||
new /obj/item/clothing/suit/storage/vest/detective(src)
|
||||
new /obj/item/ammo_magazine/c45m/rubber(src)
|
||||
new /obj/item/ammo_magazine/c45m/rubber(src)
|
||||
|
||||
@@ -186,11 +186,21 @@
|
||||
var/cremating = 0
|
||||
var/id = 1
|
||||
var/locked = 0
|
||||
var/_wifi_id
|
||||
var/datum/wifi/receiver/button/crematorium/wifi_receiver
|
||||
|
||||
/obj/structure/crematorium/initialize()
|
||||
..()
|
||||
if(_wifi_id)
|
||||
wifi_receiver = new(_wifi_id, src)
|
||||
|
||||
/obj/structure/crematorium/Destroy()
|
||||
if(connected)
|
||||
qdel(connected)
|
||||
connected = null
|
||||
if(wifi_receiver)
|
||||
qdel(wifi_receiver)
|
||||
wifi_receiver = null
|
||||
return ..()
|
||||
|
||||
/obj/structure/crematorium/proc/update()
|
||||
|
||||
@@ -26,7 +26,7 @@ var/global/list/random_junk
|
||||
if(prob(25))
|
||||
return /obj/effect/decal/cleanable/generic
|
||||
if(!random_junk)
|
||||
random_junk = subtypes(/obj/item/trash)
|
||||
random_junk = subtypesof(/obj/item/trash)
|
||||
random_junk += typesof(/obj/item/weapon/cigbutt)
|
||||
random_junk += /obj/effect/decal/cleanable/spiderling_remains
|
||||
random_junk += /obj/effect/decal/remains/mouse
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
var/max_fire_temperature_sustained = 0 //The max temperature of the fire which it was subjected to
|
||||
var/dirt = 0
|
||||
|
||||
/turf/simulated/clean_blood()
|
||||
for(var/obj/effect/decal/cleanable/blood/B in contents)
|
||||
B.fluorescent = 0
|
||||
B.invisibility = 100
|
||||
|
||||
/turf/simulated/New()
|
||||
..()
|
||||
if(istype(loc, /area/chapel))
|
||||
|
||||
@@ -216,3 +216,6 @@ var/const/enterloopsanity = 100
|
||||
if(A.density && !(A.flags & ON_BORDER))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/turf/proc/update_blood_overlays()
|
||||
return
|
||||
|
||||
@@ -101,7 +101,6 @@ var/join_motd = null
|
||||
|
||||
var/datum/nanomanager/nanomanager = new() // NanoManager, the manager for Nano UIs.
|
||||
var/datum/event_manager/event_manager = new() // Event Manager, the manager for events.
|
||||
var/datum/subsystem/alarm/alarm_manager = new() // Alarm Manager, the manager for alarms.
|
||||
|
||||
var/list/awaydestinations = list() // Away missions. A list of landmarks that the warpgate can take you to.
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
set desc = "Usage: Capture-Map-Part target_x_cord target_y_cord target_z_cord range (captures part of a map originating from bottom left corner)"
|
||||
|
||||
if(!check_rights(R_ADMIN|R_DEBUG|R_SERVER))
|
||||
usr << "You are not allowed to use this command"
|
||||
return
|
||||
|
||||
if(isnull(tx) || isnull(ty) || isnull(tz) || isnull(range))
|
||||
@@ -17,41 +18,7 @@
|
||||
return
|
||||
|
||||
if(locate(tx,ty,tz))
|
||||
var/list/turfstocapture = list()
|
||||
var/hasasked = 0
|
||||
for(var/xoff = 0 to range)
|
||||
for(var/yoff = 0 to range)
|
||||
var/turf/T = locate(tx + xoff,ty + yoff,tz)
|
||||
if(T)
|
||||
turfstocapture.Add(T)
|
||||
else
|
||||
if(!hasasked)
|
||||
var/answer = alert("Capture includes non existant turf, Continue capture?","Continue capture?", "No", "Yes")
|
||||
hasasked = 1
|
||||
if(answer == "No")
|
||||
return
|
||||
|
||||
var/list/atoms = list()
|
||||
for(var/turf/T in turfstocapture)
|
||||
atoms.Add(T)
|
||||
for(var/atom/A in T)
|
||||
if(A.invisibility) continue
|
||||
atoms.Add(A)
|
||||
|
||||
atoms = sort_atoms_by_layer(atoms)
|
||||
var/icon/cap = icon('icons/effects/96x96.dmi', "")
|
||||
cap.Scale(range*32, range*32)
|
||||
cap.Blend("#000", ICON_OVERLAY)
|
||||
for(var/atom/A in atoms)
|
||||
if(A)
|
||||
var/icon/img = getFlatIcon(A)
|
||||
if(istype(img, /icon))
|
||||
if(istype(A, /mob/living) && A:lying)
|
||||
img.BecomeLying()
|
||||
var/xoff = (A.x - tx) * 32
|
||||
var/yoff = (A.y - ty) * 32
|
||||
cap.Blend(img, blendMode2iconMode(A.blend_mode), A.pixel_x + xoff, A.pixel_y + yoff)
|
||||
|
||||
var/cap = generate_image(tx ,ty ,tz ,range, CAPTURE_MODE_PARTIAL, null, 1, 1)
|
||||
var/file_name = "map_capture_x[tx]_y[ty]_z[tz]_r[range].png"
|
||||
usr << "Saved capture in cache as [file_name]."
|
||||
usr << browse_rsc(cap, file_name)
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
spawn(5) // And wait a half-second, since it sounds like you can do this too fast.
|
||||
if(src)
|
||||
winset(src, null, "command=\".configure graphics-hwmode off\"")
|
||||
sleep(2) // wait a bit more, possibly fixes hardware mode not re-activating right
|
||||
winset(src, null, "command=\".configure graphics-hwmode on\"")
|
||||
|
||||
log_client_to_db()
|
||||
@@ -339,3 +340,9 @@ client/proc/MayRespawn()
|
||||
|
||||
// Something went wrong, client is usually kicked or transfered to a new mob at this point
|
||||
return 0
|
||||
|
||||
client/verb/character_setup()
|
||||
set name = "Character Setup"
|
||||
set category = "Preferences"
|
||||
if(prefs)
|
||||
prefs.ShowChoices(usr)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/datum/category_item/player_setup_item/general/basic
|
||||
name = "Basic"
|
||||
sort_order = 1
|
||||
var/list/valid_player_genders = list(MALE, FEMALE)
|
||||
|
||||
/datum/category_item/player_setup_item/general/basic/load_character(var/savefile/S)
|
||||
S["real_name"] >> pref.real_name
|
||||
@@ -19,12 +20,13 @@
|
||||
S["OOC_Notes"] << pref.metadata
|
||||
|
||||
/datum/category_item/player_setup_item/general/basic/sanitize_character()
|
||||
pref.real_name = sanitizeName(pref.real_name)
|
||||
pref.age = sanitize_integer(pref.age, AGE_MIN, AGE_MAX, initial(pref.age))
|
||||
pref.gender = sanitize_inlist(pref.gender, valid_player_genders, pick(valid_player_genders))
|
||||
pref.real_name = sanitize_name(pref.real_name, pref.species)
|
||||
if(!pref.real_name)
|
||||
pref.real_name = random_name(pref.gender, pref.species)
|
||||
pref.spawnpoint = sanitize_inlist(pref.spawnpoint, spawntypes, initial(pref.spawnpoint))
|
||||
pref.be_random_name = sanitize_integer(pref.be_random_name, 0, 1, initial(pref.be_random_name))
|
||||
pref.age = sanitize_integer(pref.age, AGE_MIN, AGE_MAX, initial(pref.age))
|
||||
|
||||
/datum/category_item/player_setup_item/general/basic/content()
|
||||
. = "<b>Name:</b> "
|
||||
@@ -32,7 +34,7 @@
|
||||
. += "(<a href='?src=\ref[src];random_name=1'>Random Name</A>) "
|
||||
. += "(<a href='?src=\ref[src];always_random_name=1'>Always Random Name: [pref.be_random_name ? "Yes" : "No"]</a>)"
|
||||
. += "<br>"
|
||||
. += "<b>Gender:</b> <a href='?src=\ref[src];gender=1'><b>[pref.gender == MALE ? "Male" : "Female"]</b></a><br>"
|
||||
. += "<b>Gender:</b> <a href='?src=\ref[src];gender=1'><b>[capitalize(lowertext(pref.gender))]</b></a><br>"
|
||||
. += "<b>Age:</b> <a href='?src=\ref[src];age=1'>[pref.age]</a><br>"
|
||||
. += "<b>Spawn Point</b>: <a href='?src=\ref[src];spawnpoint=1'>[pref.spawnpoint]</a><br>"
|
||||
if(config.allow_Metadata)
|
||||
@@ -42,7 +44,7 @@
|
||||
if(href_list["rename"])
|
||||
var/raw_name = input(user, "Choose your character's name:", "Character Name") as text|null
|
||||
if (!isnull(raw_name) && CanUseTopic(user))
|
||||
var/new_name = sanitizeName(raw_name)
|
||||
var/new_name = sanitize_name(raw_name, pref.species)
|
||||
if(new_name)
|
||||
pref.real_name = new_name
|
||||
return TOPIC_REFRESH
|
||||
@@ -59,10 +61,7 @@
|
||||
return TOPIC_REFRESH
|
||||
|
||||
else if(href_list["gender"])
|
||||
if(pref.gender == MALE)
|
||||
pref.gender = FEMALE
|
||||
else
|
||||
pref.gender = MALE
|
||||
pref.gender = next_in_list(pref.gender, valid_player_genders)
|
||||
return TOPIC_REFRESH
|
||||
|
||||
else if(href_list["age"])
|
||||
@@ -80,7 +79,6 @@
|
||||
pref.spawnpoint = choice
|
||||
return TOPIC_REFRESH
|
||||
|
||||
|
||||
else if(href_list["metadata"])
|
||||
var/new_metadata = sanitize(input(user, "Enter any information you'd like others to see, such as Roleplay-preferences:", "Game Preference" , pref.metadata)) as message|null
|
||||
if(new_metadata && CanUseTopic(user))
|
||||
|
||||
@@ -46,6 +46,10 @@
|
||||
selected_category = null
|
||||
return ..()
|
||||
|
||||
/datum/category_collection/player_setup_collection/proc/sanitize_setup()
|
||||
for(var/datum/category_group/player_setup_category/PS in categories)
|
||||
PS.sanitize_setup()
|
||||
|
||||
/datum/category_collection/player_setup_collection/proc/load_character(var/savefile/S)
|
||||
for(var/datum/category_group/player_setup_category/PS in categories)
|
||||
PS.load_character(S)
|
||||
@@ -100,24 +104,37 @@
|
||||
/datum/category_group/player_setup_category/dd_SortValue()
|
||||
return sort_order
|
||||
|
||||
/datum/category_group/player_setup_category/proc/sanitize_setup()
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.sanitize_preferences()
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.sanitize_character()
|
||||
|
||||
/datum/category_group/player_setup_category/proc/load_character(var/savefile/S)
|
||||
// Load all data, then sanitize it.
|
||||
// Need due to, for example, the 01_basic module relying on species having been loaded to sanitize correctly but that isn't loaded until module 03_body.
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.load_character(S)
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.sanitize_character()
|
||||
|
||||
/datum/category_group/player_setup_category/proc/save_character(var/savefile/S)
|
||||
// Sanitize all data, then save it
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.sanitize_character()
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.save_character(S)
|
||||
|
||||
/datum/category_group/player_setup_category/proc/load_preferences(var/savefile/S)
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.load_preferences(S)
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.sanitize_preferences()
|
||||
|
||||
/datum/category_group/player_setup_category/proc/save_preferences(var/savefile/S)
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.sanitize_preferences()
|
||||
for(var/datum/category_item/player_setup_item/PI in items)
|
||||
PI.save_preferences(S)
|
||||
|
||||
/datum/category_group/player_setup_category/proc/content(var/mob/user)
|
||||
|
||||
@@ -113,7 +113,6 @@ datum/preferences
|
||||
|
||||
/datum/preferences/New(client/C)
|
||||
player_setup = new(src)
|
||||
|
||||
gender = pick(MALE, FEMALE)
|
||||
real_name = random_name(gender,species)
|
||||
b_type = pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
|
||||
@@ -235,6 +234,8 @@ datum/preferences
|
||||
return 1
|
||||
|
||||
/datum/preferences/proc/copy_to(mob/living/carbon/human/character, safety = 0)
|
||||
// Sanitizing rather than saving as someone might still be editing when copy_to occurs.
|
||||
player_setup.sanitize_setup()
|
||||
if(be_random_name)
|
||||
real_name = random_name(gender,species)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ var/global/list/home_system_choices = list(
|
||||
"Sol",
|
||||
"Nyx",
|
||||
"Tau Ceti",
|
||||
"Epsilon Ursae Majoris",
|
||||
"Epsilon Ursae Minoris",
|
||||
"S'randarr"
|
||||
)
|
||||
|
||||
|
||||
@@ -85,6 +85,9 @@
|
||||
player_setup.save_character(S)
|
||||
return 1
|
||||
|
||||
/datum/preferences/proc/sanitize_preferences()
|
||||
player_setup.sanitize_setup()
|
||||
return 1
|
||||
|
||||
#undef SAVEFILE_VERSION_MAX
|
||||
#undef SAVEFILE_VERSION_MIN
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
var/flash_protection = FLASH_PROTECTION_NONE // Sets the item's level of flash protection.
|
||||
var/tint = TINT_NONE // Sets the item's level of visual impairment tint.
|
||||
var/list/species_restricted = null //Only these species can wear this kit.
|
||||
var/gunshot_residue //Used by forensics.
|
||||
|
||||
/*
|
||||
Sprites used when the clothing item is refit. This is done by setting icon_override.
|
||||
@@ -17,6 +18,11 @@
|
||||
/obj/item/clothing/proc/update_clothing_icon()
|
||||
return
|
||||
|
||||
// Aurora forensics port.
|
||||
/obj/item/clothing/clean_blood()
|
||||
..()
|
||||
gunshot_residue = null
|
||||
|
||||
//BS12: Species-restricted clothing check.
|
||||
/obj/item/clothing/mob_can_equip(M as mob, slot)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/obj/item/clothing/head/helmet/space/rig/ert
|
||||
light_overlay = "helmet_light_dual"
|
||||
camera_networks = list("ERT")
|
||||
camera_networks = list(NETWORK_ERT)
|
||||
|
||||
/obj/item/weapon/rig/ert
|
||||
name = "ERT-C hardsuit control module"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user