This commit is contained in:
Kearel
2015-11-28 11:21:07 -06:00
176 changed files with 9954 additions and 9190 deletions

View File

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

View File

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

BIN
btime.dll Normal file

Binary file not shown.

BIN
btime.so Normal file

Binary file not shown.

18
code/__defines/btime.dm Normal file
View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
/datum/controller/process/lighting/setup()
name = "lighting"
start_delay = 1
schedule_interval = 5 // every .5 second
lighting_controller.initializeLighting()

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View 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

View 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
*/

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ var/global/list/home_system_choices = list(
"Sol",
"Nyx",
"Tau Ceti",
"Epsilon Ursae Majoris",
"Epsilon Ursae Minoris",
"S'randarr"
)

View File

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

View File

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

View File

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