mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-08-29 15:08:34 +01:00
Merge branch 'master' of https://github.com/PolarisSS13/Polaris into frame_fix
Conflicts: maps/polaris-1.dmm maps/polaris-5.dmm
This commit is contained in:
@@ -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
|
||||
@@ -44,10 +44,11 @@
|
||||
#define DROPLIMB_BURN 2
|
||||
|
||||
// Damage above this value must be repaired with surgery.
|
||||
#define ROBOLIMB_SELF_REPAIR_CAP 30
|
||||
#define ROBOLIMB_REPAIR_CAP 30
|
||||
|
||||
#define ORGAN_ASSISTED 1
|
||||
#define ORGAN_ROBOT 2
|
||||
#define ORGAN_ASSISTED 1 // Like pacemakers, not robotic
|
||||
#define ORGAN_ROBOT 2 // Fully robotic, no organic parts
|
||||
#define ORGAN_LIFELIKE 3 // Robotic, made to appear organic
|
||||
|
||||
//Germs and infections.
|
||||
#define GERM_LEVEL_AMBIENT 110 // Maximum germ level you can reach by standing still.
|
||||
|
||||
+5
-2
@@ -11,7 +11,10 @@
|
||||
#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_SLEEP_INTERVAL 8 // 2 ticks
|
||||
#define PROCESS_DEFAULT_CPU_THRESHOLD 90 // 90%
|
||||
|
||||
//#define UPDATE_QUEUE_DEBUG
|
||||
// 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)
|
||||
@@ -12,7 +12,8 @@
|
||||
#define TECH_ILLEGAL "syndicate"
|
||||
#define TECH_ARCANE "arcane"
|
||||
|
||||
#define IMPRINTER 0x1 //For circuits. Uses glass/chemicals.
|
||||
#define PROTOLATHE 0x2 //New stuff. Uses glass/metal/chemicals
|
||||
#define MECHFAB 0x4 //Mechfab
|
||||
#define CHASSIS 0x8 //For protolathe, but differently
|
||||
#define IMPRINTER 0x0001 //For circuits. Uses glass/chemicals.
|
||||
#define PROTOLATHE 0x0002 //New stuff. Uses glass/metal/chemicals
|
||||
#define MECHFAB 0x0004 //Mechfab
|
||||
#define CHASSIS 0x0008 //For protolathe, but differently
|
||||
#define PROSFAB 0x0010 //For prosthetics fab
|
||||
@@ -0,0 +1 @@
|
||||
var/datum/gear_tweak/color/gear_tweak_free_color_choice = new()
|
||||
@@ -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
|
||||
|
||||
+20
-20
@@ -248,31 +248,31 @@
|
||||
. |= M // Since we're already looping through mobs, why bother using |= ? This only slows things down.
|
||||
return .
|
||||
|
||||
/proc/get_mobs_and_objs_in_view_fast(var/turf/T, var/range)
|
||||
var/list/results = list("objs" = list(), "mobs" = list())
|
||||
/proc/get_mobs_and_objs_in_view_fast(var/turf/T, var/range, var/checkghosts = 1)
|
||||
|
||||
var/list/hear = dview(range,T,INVISIBILITY_MAXIMUM)
|
||||
var/list/hearturfs = list()
|
||||
var/list/mobs = list()
|
||||
var/list/objs = list()
|
||||
|
||||
for(var/I in hear)
|
||||
if(ismob(I))
|
||||
var/mob/M = I
|
||||
results["mobs"] += M
|
||||
hearturfs += M.locs[1]
|
||||
else if(isobj(I))
|
||||
var/obj/O = I
|
||||
results["objs"] += I
|
||||
hearturfs += O.locs[1]
|
||||
var/list/hear = dview(range,T,INVISIBILITY_MAXIMUM)
|
||||
var/list/hearturfs = list()
|
||||
|
||||
for(var/atom/movable/AM in hear)
|
||||
if(ismob(AM))
|
||||
mobs += AM
|
||||
hearturfs += AM.locs[1]
|
||||
else if(isobj(AM))
|
||||
objs += AM
|
||||
hearturfs += AM.locs[1]
|
||||
|
||||
|
||||
for(var/mob/M in player_list)
|
||||
if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
|
||||
results["mobs"] |= M
|
||||
continue
|
||||
if(M.loc && M.locs[1] in hearturfs)
|
||||
results["mobs"] |= M
|
||||
for(var/mob/M in player_list)
|
||||
if(checkghosts && M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
|
||||
mobs |= M
|
||||
continue
|
||||
if(M.loc && M.locs[1] in hearturfs)
|
||||
mobs |= M
|
||||
|
||||
return results
|
||||
return list("mobs" = mobs, "objs" = objs)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ var/global/list/language_keys[0] // Table of say codes for all languages
|
||||
var/global/list/whitelisted_species = list("Human") // Species that require a whitelist check.
|
||||
var/global/list/playable_species = list("Human") // A list of ALL playable species, whitelisted, latejoin or otherwise.
|
||||
|
||||
var/list/mannequins_
|
||||
|
||||
// Posters
|
||||
var/global/list/poster_designs = list()
|
||||
|
||||
@@ -49,45 +51,7 @@ var/global/list/facial_hair_styles_male_list = list()
|
||||
var/global/list/facial_hair_styles_female_list = list()
|
||||
var/global/list/skin_styles_female_list = list() //unused
|
||||
//Underwear
|
||||
var/global/list/underwear_top_t = list(
|
||||
"Bra, Red" = "t1", "Bra, White" = "t2", "Bra, Yellow" = "t3", "Bra, Blue" = "t4", "Bra, Black" = "t5", "Lacy Bra" = "t6", "Sports Bra, Black" = "t7", "Sports Bra, White" = "t8",
|
||||
"Sports Bra Alt, Black" = "t9", "Sporta Bra Alt, White" = "t10", "Bra, Baby-Blue" = "t11", "Bra, Green" = "t12", "Bra, Pink" = "t13", "Bra, Violet" = "t14",
|
||||
"Lacy Bra Alt" = "t15", "Lacy Bra Alt, Violet" = "t16", "Halterneck Bra, Black" = "t17", "Halterneck Bra, Blue" = "t18", "Halterneck Bra, Green" = "t19", "Halterneck Bra, Purple" = "t20",
|
||||
"Halterneck Bra, Red" = "t21", "Halterneck Bra, Teal" = "t22", "Halterneck Bra, Violet" = "t23", "Halterneck Bra, White" = "t24", "None")
|
||||
var/global/list/underwear_bottom_t = list(
|
||||
"Briefs, White" = "b1", "Briefs, Grey" = "b2", "Briefs, Green" = "b3", "Briefs, Blue" = "b4", "Briefs, Black" = "b5", "Boxers, Loveheart" = "b7", "Boxers, Black" = "b8",
|
||||
"Boxers, Grey" = "b9", "Boxers, Green & Blue Striped" = "b10", "Panties, Red" = "b11", "Panties, White" = "b12", "Panties, Yellow" = "b13", "Panties, Blue" = "b14",
|
||||
"Panties, Light-Black" = "b15", "Thong" = "b16", "Panties, Black" = "b17", "Panties Alt, White" = "b18", "Compression Shorts, Black" = "b19", "Compression Shorts, White" = "b20",
|
||||
"Compression Shorts, Baby-Blue" = "b21", "Panties, Green" = "b22", "Compression Shorts, Pink" = "b23", "Thong, Violet" = "b24", "Thong Alt" = "b25", "Thong Alt, Violet" = "b26",
|
||||
"Alt Thong, Black" = "b27", "Alt Thong, Blue" = "b28", "Alt Thong, Green" = "b29", "Alt Thong, Purple" = "b30", "Alt Thong, Red" = "b31", "Alt Thong, Teal" = "b32",
|
||||
"Alt Thong, Violet" = "b33", "Alt Thong, White" = "b34", "None")
|
||||
//undershirt
|
||||
var/global/list/undershirt_t = list(
|
||||
"White tank top" = "u1", "Black tank top" = "u2", "Black shirt" = "u3",
|
||||
"White shirt" = "u4", "White shirt 2" = "shirt_white_s", "White tank top 2" = "tank_white_s",
|
||||
"Black shirt 2" = "shirt_black_s", "Grey shirt" = "shirt_grey_s", "Heart shirt" = "lover_s",
|
||||
"I love NT shirt" = "ilovent_s", "White shortsleeve shirt" = "whiteshortsleeve_s", "Purple shortsleeve shirt" = "purpleshortsleeve_s",
|
||||
"Blue shortsleeve shirt" = "blueshortsleeve_s", "Green shortsleeve shirt" = "greenshortsleeve_s", "Black shortsleeve shirt" = "blackshortsleeve_s",
|
||||
"Blue shirt" = "blueshirt_s", "Red shirt" = "redshirt_s", "Yellow shirt" = "yellowshirt_s", "Green shirt" = "greenshirt_s",
|
||||
"Blue polo shirt" = "bluepolo_s", "Red polo shirt" = "redpolo_s", "White polo shirt" = "whitepolo_s",
|
||||
"Grey-yellow polo shirt" = "grayyellowpolo_s", "Fire tank top" = "tank_fire_s", "NT shirt" = "shirt_nano_s",
|
||||
"Blue shirt 2" = "shirt_blue_s", "Red shirt 2" = "shirt_red_s", "Red tank top" = "tank_red_s", "Green shirt 2" = "shirt_green_s",
|
||||
"Tiedye shirt" = "shirt_tiedye_s", "Green sport shirt" = "greenshirtsport_s", "Red sport shirt" = "redshirtsport_s",
|
||||
"Blue striped shirt" = "shirt_stripes_s", "Blue sport shirt" = "blueshirtsport_s", "None")
|
||||
//Socks
|
||||
var/global/list/socks_t = list(
|
||||
"White normal" = "white_norm", "White short" = "white_short", "White knee" = "white_knee",
|
||||
"White thigh" = "white_thigh", "Black normal" = "black_norm", "Black short" = "black_short",
|
||||
"Black knee" = "black_knee", "Black thigh" = "black_thigh", "Thin knee" = "thin_knee",
|
||||
"Thin thigh" = "thin_thigh", "Pantyhose" = "pantyhose", "Striped thigh" = "striped_thigh",
|
||||
"Striped knee" = "striped_knee", "Rainbow knee" = "rainbow_knee", "Rainbow thigh" = "rainbow_thigh",
|
||||
"Fishnets" = "fishnet", "Thin white thigh" = "thinwhite_thigh", "Thin white knee" = "thinwhite_knee",
|
||||
"Green striped thigh" = "gstriped_thigh", "Green striped knee" = "gstriped_knee",
|
||||
"Purple striped thigh" = "pstriped_thigh", "Purple striped knee" = "pstriped_knee",
|
||||
"Blue striped thigh" = "bstriped_thigh", "Blue striped knee" = "bstriped_knee",
|
||||
"Yellow striped thigh" = "ystriped_thigh", "Yellow striped knee" = "ystriped_knee",
|
||||
"Red striped thigh" = "rstriped_thigh", "Red striped knee" = "rstriped_knee",
|
||||
"Orange striped thigh" = "ostriped_thigh", "Orange striped knee" = "ostriped_knee", "None")
|
||||
var/datum/category_collection/underwear/global_underwear = new()
|
||||
|
||||
//Backpacks
|
||||
var/global/list/backbaglist = list("Nothing", "Backpack", "Satchel", "Satchel Alt")
|
||||
@@ -137,6 +101,14 @@ var/global/list/string_slot_flags = list(
|
||||
"holster" = SLOT_HOLSTER
|
||||
)
|
||||
|
||||
/proc/get_mannequin(var/ckey)
|
||||
if(!mannequins_)
|
||||
mannequins_ = new()
|
||||
. = mannequins_[ckey]
|
||||
if(!.)
|
||||
. = new/mob/living/carbon/human/dummy/mannequin()
|
||||
mannequins_[ckey] = .
|
||||
|
||||
//////////////////////////
|
||||
/////Initial Building/////
|
||||
//////////////////////////
|
||||
|
||||
@@ -602,13 +602,13 @@ proc/dd_sortedTextList(list/incoming)
|
||||
/datum/alarm/dd_SortValue()
|
||||
return "[sanitize_old(last_name)]"
|
||||
|
||||
/proc/subtypes(prototype)
|
||||
/proc/subtypesof(prototype)
|
||||
return (typesof(prototype) - prototype)
|
||||
|
||||
//creates every subtype of prototype (excluding prototype) and adds it to list L.
|
||||
//if no list/L is provided, one is created.
|
||||
/proc/init_subtypes(prototype, list/L)
|
||||
if(!istype(L)) L = list()
|
||||
for(var/path in subtypes(prototype))
|
||||
for(var/path in subtypesof(prototype))
|
||||
L += new path()
|
||||
return L
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
if (config.log_admin)
|
||||
diary << "\[[time_stamp()]]ADMIN: [text][log_end]"
|
||||
|
||||
|
||||
/proc/log_debug(text)
|
||||
if (config.log_debug)
|
||||
diary << "\[[time_stamp()]]DEBUG: [text][log_end]"
|
||||
@@ -34,7 +33,6 @@
|
||||
if(C.is_preference_enabled(/datum/client_preference/debug/show_debug_logs))
|
||||
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]"
|
||||
|
||||
|
||||
@@ -38,3 +38,5 @@
|
||||
#define isslime(A) istype(A, /mob/living/carbon/slime)
|
||||
|
||||
#define isxeno(A) istype(A, /mob/living/simple_animal/xeno)
|
||||
|
||||
#define RANDOM_BLOOD_TYPE pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
|
||||
|
||||
@@ -388,10 +388,7 @@
|
||||
f_style = "Shaved"
|
||||
if(dna.species == "Human") //no more xenos losing ears/tentacles
|
||||
h_style = pick("Bedhead", "Bedhead 2", "Bedhead 3")
|
||||
undershirt = null
|
||||
underwear_top = null
|
||||
underwear_bottom = null
|
||||
socks = null
|
||||
all_underwear.Cut()
|
||||
regenerate_icons()
|
||||
|
||||
/obj/screen/ling
|
||||
|
||||
@@ -1,40 +1,59 @@
|
||||
/*
|
||||
=== Item Click Call Sequences ===
|
||||
These are the default click code call sequences used when clicking on stuff with an item.
|
||||
|
||||
Atoms:
|
||||
|
||||
mob/ClickOn() calls the item's resolve_attackby() proc.
|
||||
item/resolve_attackby() calls the target atom's attackby() proc.
|
||||
|
||||
Mobs:
|
||||
|
||||
mob/living/attackby() after checking for surgery, calls the item's attack() proc.
|
||||
item/attack() generates attack logs, sets click cooldown and calls the mob's attacked_with_item() proc. If you override this, consider whether you need to set a click cooldown, play attack animations, and generate logs yourself.
|
||||
mob/attacked_with_item() should then do mob-type specific stuff (like determining hit/miss, handling shields, etc) and then possibly call the item's apply_hit_effect() proc to actually apply the effects of being hit.
|
||||
|
||||
Item Hit Effects:
|
||||
|
||||
item/apply_hit_effect() can be overriden to do whatever you want. However "standard" physical damage based weapons should make use of the target mob's hit_with_weapon() proc to
|
||||
avoid code duplication. This includes items that may sometimes act as a standard weapon in addition to having other effects (e.g. stunbatons on harm intent).
|
||||
*/
|
||||
|
||||
// Called when the item is in the active hand, and clicked; alternately, there is an 'activate held object' verb or you can hit pagedown.
|
||||
/obj/item/proc/attack_self(mob/user)
|
||||
return
|
||||
|
||||
//I would prefer to rename this to attack(), but that would involve touching hundreds of files.
|
||||
/obj/item/proc/resolve_attackby(atom/A, mob/user)
|
||||
add_fingerprint(user)
|
||||
return A.attackby(src, user)
|
||||
|
||||
// No comment
|
||||
/atom/proc/attackby(obj/item/W, mob/user)
|
||||
return
|
||||
|
||||
/atom/movable/attackby(obj/item/W, mob/user)
|
||||
if(!(W.flags&NOBLUDGEON))
|
||||
if(!(W.flags & NOBLUDGEON))
|
||||
visible_message("<span class='danger'>[src] has been hit by [user] with [W].</span>")
|
||||
|
||||
/mob/living/attackby(obj/item/I, mob/user)
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
if(istype(I) && ismob(user))
|
||||
I.attack(src, user)
|
||||
|
||||
if(!ismob(user))
|
||||
return 0
|
||||
if(can_operate(src) && do_surgery(src,user,I)) //Surgery
|
||||
return 1
|
||||
return I.attack(src, user, user.zone_sel.selecting)
|
||||
|
||||
// Proximity_flag is 1 if this afterattack was called on something adjacent, in your square, or on your person.
|
||||
// Click parameters is the params string from byond Click() code, see that documentation.
|
||||
/obj/item/proc/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
|
||||
return
|
||||
|
||||
//TODO: refactor mob attack code.
|
||||
/*
|
||||
Busy writing something else that I don't want to get mixed up in a general attack code, and I don't want to forget this so leaving a note here.
|
||||
leave attackby() as handling the general case of "using an item on a mob"
|
||||
attackby() will decide to call attacked_by() or not.
|
||||
attacked_by() will be made a living level proc and handle the specific case of "attacking with an item to cause harm"
|
||||
attacked_by() will then call attack() so that stunbatons and other weapons that have special attack effects can do their thing.
|
||||
attacked_by() will handle hitting/missing/logging as it does now, and will call attack() to apply the attack effects (damage) instead of the other way around (as it is now).
|
||||
*/
|
||||
|
||||
/obj/item/proc/attack(mob/living/M as mob, mob/living/user as mob, def_zone)
|
||||
|
||||
if(!istype(M) || (can_operate(M) && do_surgery(M,user,src))) return 0
|
||||
//I would prefer to rename this attack_as_weapon(), but that would involve touching hundreds of files.
|
||||
/obj/item/proc/attack(mob/living/M, mob/living/user, var/target_zone)
|
||||
if(!force || (flags & NOBLUDGEON))
|
||||
return 0
|
||||
if(M == user && user.a_intent != I_HURT)
|
||||
return 0
|
||||
|
||||
/////////////////////////
|
||||
user.lastattacked = M
|
||||
@@ -46,50 +65,22 @@ attacked_by() will handle hitting/missing/logging as it does now, and will call
|
||||
msg_admin_attack("[key_name(user)] attacked [key_name(M)] with [name] (INTENT: [uppertext(user.a_intent)]) (DAMTYE: [uppertext(damtype)])" )
|
||||
/////////////////////////
|
||||
|
||||
// Attacking someone with a weapon while they are neck-grabbed
|
||||
if(user.a_intent == I_HURT)
|
||||
for(var/obj/item/weapon/grab/G in M.grabbed_by)
|
||||
if(G.assailant == user && G.state >= GRAB_NECK)
|
||||
M.attack_throat(src, G, user)
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
user.do_attack_animation(M)
|
||||
|
||||
var/hit_zone = M.resolve_item_attack(src, user, target_zone)
|
||||
if(hit_zone)
|
||||
apply_hit_effect(M, user, hit_zone)
|
||||
|
||||
return 1
|
||||
|
||||
//Called when a weapon is used to make a successful melee attack on a mob. Returns the blocked result
|
||||
/obj/item/proc/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone)
|
||||
if(hitsound)
|
||||
playsound(loc, hitsound, 50, 1, -1)
|
||||
|
||||
var/power = force
|
||||
if(HULK in user.mutations)
|
||||
power *= 2
|
||||
return target.hit_with_weapon(src, user, power, hit_zone)
|
||||
|
||||
// TODO: needs to be refactored into a mob/living level attacked_by() proc. ~Z
|
||||
user.do_attack_animation(M)
|
||||
user.break_cloak()
|
||||
if(istype(M, /mob/living/carbon/human))
|
||||
var/mob/living/carbon/human/H = M
|
||||
|
||||
// Handle striking to cripple.
|
||||
var/dislocation_str
|
||||
if(user.a_intent == I_DISARM)
|
||||
dislocation_str = H.attack_joint(src, user, def_zone)
|
||||
if(H.attacked_by(src, user, def_zone) && hitsound)
|
||||
playsound(loc, hitsound, 50, 1, -1)
|
||||
spawn(1) //ugh I hate this but I don't want to root through human attack procs to print it after this call resolves.
|
||||
if(dislocation_str) user.visible_message("<span class='danger'>[dislocation_str]</span>")
|
||||
return 1
|
||||
return 0
|
||||
else
|
||||
if(attack_verb.len)
|
||||
user.visible_message("<span class='danger'>[M] has been [pick(attack_verb)] with [src] by [user]!</span>")
|
||||
else
|
||||
user.visible_message("<span class='danger'>[M] has been attacked with [src] by [user]!</span>")
|
||||
|
||||
if (hitsound)
|
||||
playsound(loc, hitsound, 50, 1, -1)
|
||||
switch(damtype)
|
||||
if("brute")
|
||||
M.take_organ_damage(power)
|
||||
if(prob(33)) // Added blood for whacking non-humans too
|
||||
var/turf/simulated/location = get_turf(M)
|
||||
if(istype(location)) location.add_blood_floor(M)
|
||||
if("fire")
|
||||
if (!(COLD_RESISTANCE in M.mutations))
|
||||
M.take_organ_damage(0, power)
|
||||
M << "Aargh it burns!"
|
||||
M.updatehealth()
|
||||
add_fingerprint(user)
|
||||
return 1
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// DM Environment file for ProcessScheduler.dme.
|
||||
// All manual changes should be made outside the BEGIN_ and END_ blocks.
|
||||
// New source code should be placed in .dm files: choose File/New --> Code File.
|
||||
|
||||
// BEGIN_INTERNALS
|
||||
// END_INTERNALS
|
||||
|
||||
// BEGIN_FILE_DIR
|
||||
#define FILE_DIR .
|
||||
// END_FILE_DIR
|
||||
|
||||
// BEGIN_PREFERENCES
|
||||
// END_PREFERENCES
|
||||
|
||||
// BEGIN_INCLUDE
|
||||
#include "core\_define.dm"
|
||||
#include "core\_stubs.dm"
|
||||
#include "core\process.dm"
|
||||
#include "core\processScheduler.dm"
|
||||
#include "core\updateQueue.dm"
|
||||
#include "core\updateQueueWorker.dm"
|
||||
#include "test\processSchedulerView.dm"
|
||||
#include "test\testDyingUpdateQueueProcess.dm"
|
||||
#include "test\testHarness.dm"
|
||||
#include "test\testHungProcess.dm"
|
||||
#include "test\testNiceProcess.dm"
|
||||
#include "test\testSlowProcess.dm"
|
||||
#include "test\testUpdateQueue.dm"
|
||||
#include "test\testUpdateQueueProcess.dm"
|
||||
#include "test\testZombieProcess.dm"
|
||||
// END_INCLUDE
|
||||
|
||||
@@ -4,15 +4,7 @@
|
||||
* This file contains constructs that the process scheduler expects to exist
|
||||
* in a standard ss13 fork.
|
||||
*/
|
||||
/*
|
||||
/**
|
||||
* message_admins
|
||||
*
|
||||
* sends a message to admins
|
||||
*/
|
||||
/proc/message_admins(msg)
|
||||
world << msg
|
||||
*/
|
||||
|
||||
/**
|
||||
* logTheThing
|
||||
*
|
||||
@@ -25,14 +17,3 @@
|
||||
world << "Diary: \[[diaryType]:[type]] [text]"
|
||||
else
|
||||
world << "Log: \[[type]] [text]"
|
||||
|
||||
/**
|
||||
* var/disposed
|
||||
*
|
||||
* In goonstation, disposed is set to 1 after an object enters the delete queue
|
||||
* or the object is placed in an object pool (effectively out-of-play so to speak)
|
||||
*/
|
||||
/datum/var/disposed
|
||||
// Garbage collection (controller).
|
||||
/datum/var/gcDestroyed
|
||||
/datum/var/timeDestroyed
|
||||
@@ -48,7 +48,7 @@
|
||||
// This controls how often the process will yield (call sleep(0)) while it is running.
|
||||
// Every concurrent process should sleep periodically while running in order to allow other
|
||||
// processes to execute concurrently.
|
||||
var/tmp/sleep_interval = PROCESS_DEFAULT_SLEEP_INTERVAL
|
||||
var/tmp/sleep_interval
|
||||
|
||||
// hang_warning_time - this is the time (in 1/10 seconds) after which the server will begin to show "maybe hung" in the context window
|
||||
var/tmp/hang_warning_time = PROCESS_DEFAULT_HANG_WARNING_TIME
|
||||
@@ -59,20 +59,20 @@
|
||||
// hang_restart_time - After this much time(in 1/10 seconds), the server will automatically kill and restart the process.
|
||||
var/tmp/hang_restart_time = PROCESS_DEFAULT_HANG_RESTART_TIME
|
||||
|
||||
// cpu_threshold - if world.cpu >= cpu_threshold, scheck() will call sleep(1) to defer further work until the next tick. This keeps a process from driving a tick into overtime (causing perceptible lag)
|
||||
var/tmp/cpu_threshold = PROCESS_DEFAULT_CPU_THRESHOLD
|
||||
|
||||
// How many times in the current run has the process deferred work till the next tick?
|
||||
var/tmp/cpu_defer_count = 0
|
||||
|
||||
// How many SCHECKs have been skipped (to limit btime calls)
|
||||
var/tmp/calls_since_last_scheck = 0
|
||||
|
||||
/**
|
||||
* recordkeeping vars
|
||||
*/
|
||||
|
||||
// Records the time (server ticks) at which the process last finished sleeping
|
||||
// Records the time (1/10s timeofday) at which the process last finished sleeping
|
||||
var/tmp/last_slept = 0
|
||||
|
||||
// Records the time (s-ticks) at which the process last began running
|
||||
// Records the time (1/10s timeofday) at which the process last began running
|
||||
var/tmp/run_start = 0
|
||||
|
||||
// Records the number of times this process has been killed and restarted
|
||||
@@ -85,26 +85,33 @@
|
||||
|
||||
var/tmp/last_object
|
||||
|
||||
datum/controller/process/New(var/datum/controller/processScheduler/scheduler)
|
||||
// Counts the number of times an exception has occurred; gets reset after 10
|
||||
var/tmp/list/exceptions = list()
|
||||
|
||||
// Number of deciseconds to delay before starting the process
|
||||
var/start_delay = 0
|
||||
|
||||
/datum/controller/process/New(var/datum/controller/processScheduler/scheduler)
|
||||
..()
|
||||
main = scheduler
|
||||
previousStatus = "idle"
|
||||
idle()
|
||||
name = "process"
|
||||
schedule_interval = 50
|
||||
sleep_interval = 2
|
||||
sleep_interval = world.tick_lag / PROCESS_DEFAULT_SLEEP_INTERVAL
|
||||
last_slept = 0
|
||||
run_start = 0
|
||||
ticks = 0
|
||||
last_task = 0
|
||||
last_object = null
|
||||
|
||||
datum/controller/process/proc/started()
|
||||
/datum/controller/process/proc/started()
|
||||
var/timeofhour = TimeOfHour
|
||||
// Initialize last_slept so we can know when to sleep
|
||||
last_slept = world.timeofday
|
||||
last_slept = timeofhour
|
||||
|
||||
// Initialize run_start so we can detect hung processes.
|
||||
run_start = world.timeofday
|
||||
run_start = timeofhour
|
||||
|
||||
// Initialize defer count
|
||||
cpu_defer_count = 0
|
||||
@@ -114,65 +121,65 @@ datum/controller/process/proc/started()
|
||||
|
||||
onStart()
|
||||
|
||||
datum/controller/process/proc/finished()
|
||||
/datum/controller/process/proc/finished()
|
||||
ticks++
|
||||
idle()
|
||||
main.processFinished(src)
|
||||
|
||||
onFinish()
|
||||
|
||||
datum/controller/process/proc/doWork()
|
||||
/datum/controller/process/proc/doWork()
|
||||
|
||||
datum/controller/process/proc/setup()
|
||||
/datum/controller/process/proc/setup()
|
||||
|
||||
datum/controller/process/proc/process()
|
||||
/datum/controller/process/proc/process()
|
||||
started()
|
||||
doWork()
|
||||
finished()
|
||||
|
||||
datum/controller/process/proc/running()
|
||||
/datum/controller/process/proc/running()
|
||||
idle = 0
|
||||
queued = 0
|
||||
running = 1
|
||||
hung = 0
|
||||
setStatus(PROCESS_STATUS_RUNNING)
|
||||
|
||||
datum/controller/process/proc/idle()
|
||||
/datum/controller/process/proc/idle()
|
||||
queued = 0
|
||||
running = 0
|
||||
idle = 1
|
||||
hung = 0
|
||||
setStatus(PROCESS_STATUS_IDLE)
|
||||
|
||||
datum/controller/process/proc/queued()
|
||||
/datum/controller/process/proc/queued()
|
||||
idle = 0
|
||||
running = 0
|
||||
queued = 1
|
||||
hung = 0
|
||||
setStatus(PROCESS_STATUS_QUEUED)
|
||||
|
||||
datum/controller/process/proc/hung()
|
||||
/datum/controller/process/proc/hung()
|
||||
hung = 1
|
||||
setStatus(PROCESS_STATUS_HUNG)
|
||||
|
||||
datum/controller/process/proc/handleHung()
|
||||
/datum/controller/process/proc/handleHung()
|
||||
var/timeofhour = TimeOfHour
|
||||
var/datum/lastObj = last_object
|
||||
var/lastObjType = "null"
|
||||
if(istype(lastObj))
|
||||
lastObjType = lastObj.type
|
||||
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if (world.timeofday < run_start)
|
||||
run_start -= 864000
|
||||
|
||||
var/msg = "[name] process hung at tick #[ticks]. Process was unresponsive for [(world.timeofday - run_start) / 10] seconds and was restarted. Last task: [last_task]. Last Object Type: [lastObjType]"
|
||||
// If timeofhour has rolled over, then we need to adjust.
|
||||
if (timeofhour < run_start)
|
||||
run_start -= 36000
|
||||
var/msg = "[name] process hung at tick #[ticks]. Process was unresponsive for [(timeofhour - run_start) / 10] seconds and was restarted. Last task: [last_task]. Last Object Type: [lastObjType]"
|
||||
logTheThing("debug", null, null, msg)
|
||||
logTheThing("diary", null, null, msg, "debug")
|
||||
message_admins(msg)
|
||||
|
||||
main.restartProcess(src.name)
|
||||
|
||||
datum/controller/process/proc/kill()
|
||||
/datum/controller/process/proc/kill()
|
||||
if (!killed)
|
||||
var/msg = "[name] process was killed at tick #[ticks]."
|
||||
logTheThing("debug", null, null, msg)
|
||||
@@ -182,59 +189,68 @@ datum/controller/process/proc/kill()
|
||||
// Allow inheritors to clean up if needed
|
||||
onKill()
|
||||
|
||||
killed = TRUE
|
||||
// This should del
|
||||
del(src)
|
||||
|
||||
del(src) // This should del
|
||||
|
||||
datum/controller/process/proc/scheck(var/tickId = 0)
|
||||
// Do not call this directly - use SHECK or SCHECK_EVERY
|
||||
/datum/controller/process/proc/sleepCheck(var/tickId = 0)
|
||||
calls_since_last_scheck = 0
|
||||
if (killed)
|
||||
// The kill proc is the only place where killed is set.
|
||||
// The kill proc should have deleted this datum, and all sleeping procs that are
|
||||
// owned by it.
|
||||
CRASH("A killed process is still running somehow...")
|
||||
if (hung)
|
||||
// This will only really help if the doWork proc ends up in an infinite loop.
|
||||
handleHung()
|
||||
CRASH("Process [name] hung and was restarted.")
|
||||
|
||||
// For each tick the process defers, it increments the cpu_defer_count so we don't
|
||||
// defer indefinitely
|
||||
if (world.cpu >= cpu_threshold + cpu_defer_count * 10)
|
||||
sleep(1)
|
||||
if (main.getCurrentTickElapsedTime() > main.timeAllowance)
|
||||
sleep(world.tick_lag)
|
||||
cpu_defer_count++
|
||||
last_slept = world.timeofday
|
||||
last_slept = TimeOfHour
|
||||
else
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if (world.timeofday < last_slept)
|
||||
last_slept -= 864000
|
||||
var/timeofhour = TimeOfHour
|
||||
// If timeofhour has rolled over, then we need to adjust.
|
||||
if (timeofhour < last_slept)
|
||||
last_slept -= 36000
|
||||
|
||||
if (world.timeofday > last_slept + sleep_interval)
|
||||
// If we haven't slept in sleep_interval ticks, sleep to allow other work to proceed.
|
||||
if (timeofhour > last_slept + sleep_interval)
|
||||
// If we haven't slept in sleep_interval deciseconds, sleep to allow other work to proceed.
|
||||
sleep(0)
|
||||
last_slept = world.timeofday
|
||||
last_slept = TimeOfHour
|
||||
|
||||
datum/controller/process/proc/update()
|
||||
/datum/controller/process/proc/update()
|
||||
// Clear delta
|
||||
if(previousStatus != status)
|
||||
setStatus(status)
|
||||
|
||||
var/elapsedTime = getElapsedTime()
|
||||
|
||||
if (elapsedTime > hang_restart_time)
|
||||
if (hung)
|
||||
handleHung()
|
||||
return
|
||||
else if (elapsedTime > hang_restart_time)
|
||||
hung()
|
||||
else if (elapsedTime > hang_alert_time)
|
||||
setStatus(PROCESS_STATUS_PROBABLY_HUNG)
|
||||
else if (elapsedTime > hang_warning_time)
|
||||
setStatus(PROCESS_STATUS_MAYBE_HUNG)
|
||||
|
||||
datum/controller/process/proc/getElapsedTime()
|
||||
if (world.timeofday < run_start)
|
||||
return world.timeofday - (run_start - 864000)
|
||||
return world.timeofday - run_start
|
||||
|
||||
datum/controller/process/proc/tickDetail()
|
||||
/datum/controller/process/proc/getElapsedTime()
|
||||
var/timeofhour = TimeOfHour
|
||||
if (timeofhour < run_start)
|
||||
return timeofhour - (run_start - 36000)
|
||||
return timeofhour - run_start
|
||||
|
||||
/datum/controller/process/proc/tickDetail()
|
||||
return
|
||||
|
||||
datum/controller/process/proc/getContext()
|
||||
/datum/controller/process/proc/getContext()
|
||||
return "<tr><td>[name]</td><td>[main.averageRunTime(src)]</td><td>[main.last_run_time[src]]</td><td>[main.highest_run_time[src]]</td><td>[ticks]</td></tr>\n"
|
||||
|
||||
datum/controller/process/proc/getContextData()
|
||||
/datum/controller/process/proc/getContextData()
|
||||
return list(
|
||||
"name" = name,
|
||||
"averageRunTime" = main.averageRunTime(src),
|
||||
@@ -246,10 +262,10 @@ datum/controller/process/proc/getContextData()
|
||||
"disabled" = disabled
|
||||
)
|
||||
|
||||
datum/controller/process/proc/getStatus()
|
||||
/datum/controller/process/proc/getStatus()
|
||||
return status
|
||||
|
||||
datum/controller/process/proc/getStatusText(var/s = 0)
|
||||
/datum/controller/process/proc/getStatusText(var/s = 0)
|
||||
if(!s)
|
||||
s = status
|
||||
switch(s)
|
||||
@@ -268,21 +284,21 @@ datum/controller/process/proc/getStatusText(var/s = 0)
|
||||
else
|
||||
return "UNKNOWN"
|
||||
|
||||
datum/controller/process/proc/getPreviousStatus()
|
||||
/datum/controller/process/proc/getPreviousStatus()
|
||||
return previousStatus
|
||||
|
||||
datum/controller/process/proc/getPreviousStatusText()
|
||||
/datum/controller/process/proc/getPreviousStatusText()
|
||||
return getStatusText(previousStatus)
|
||||
|
||||
datum/controller/process/proc/setStatus(var/newStatus)
|
||||
/datum/controller/process/proc/setStatus(var/newStatus)
|
||||
previousStatus = status
|
||||
status = newStatus
|
||||
|
||||
datum/controller/process/proc/setLastTask(var/task, var/object)
|
||||
/datum/controller/process/proc/setLastTask(var/task, var/object)
|
||||
last_task = task
|
||||
last_object = object
|
||||
|
||||
datum/controller/process/proc/_copyStateFrom(var/datum/controller/process/target)
|
||||
/datum/controller/process/proc/_copyStateFrom(var/datum/controller/process/target)
|
||||
main = target.main
|
||||
name = target.name
|
||||
schedule_interval = target.schedule_interval
|
||||
@@ -295,28 +311,62 @@ datum/controller/process/proc/_copyStateFrom(var/datum/controller/process/target
|
||||
last_object = target.last_object
|
||||
copyStateFrom(target)
|
||||
|
||||
datum/controller/process/proc/copyStateFrom(var/datum/controller/process/target)
|
||||
/datum/controller/process/proc/copyStateFrom(var/datum/controller/process/target)
|
||||
|
||||
datum/controller/process/proc/onKill()
|
||||
/datum/controller/process/proc/onKill()
|
||||
|
||||
datum/controller/process/proc/onStart()
|
||||
/datum/controller/process/proc/onStart()
|
||||
|
||||
datum/controller/process/proc/onFinish()
|
||||
/datum/controller/process/proc/onFinish()
|
||||
|
||||
datum/controller/process/proc/disable()
|
||||
/datum/controller/process/proc/disable()
|
||||
disabled = 1
|
||||
|
||||
datum/controller/process/proc/enable()
|
||||
/datum/controller/process/proc/enable()
|
||||
disabled = 0
|
||||
|
||||
/datum/controller/process/proc/getAverageRunTime()
|
||||
return main.averageRunTime(src)
|
||||
/datum/controller/process/proc/getLastRunTime()
|
||||
return main.getProcessLastRunTime(src)
|
||||
|
||||
/datum/controller/process/proc/getHighestRunTime()
|
||||
return main.getProcessHighestRunTime(src)
|
||||
|
||||
/datum/controller/process/proc/getTicks()
|
||||
return ticks
|
||||
|
||||
/datum/controller/process/proc/getStatName()
|
||||
return name
|
||||
/datum/controller/process/proc/statProcess()
|
||||
var/averageRunTime = round(getAverageRunTime(), 0.1)/10
|
||||
var/lastRunTime = round(getLastRunTime(), 0.1)/10
|
||||
var/highestRunTime = round(getHighestRunTime(), 0.1)/10
|
||||
stat("[name]", "T#[getTicks()] | AR [averageRunTime] | LR [lastRunTime] | HR [highestRunTime] | D [cpu_defer_count]")
|
||||
|
||||
/datum/controller/process/proc/getTickTime()
|
||||
return "#[getTicks()]\t- [getLastRunTime()]"
|
||||
/datum/controller/process/proc/catchException(var/exception/e, var/thrower)
|
||||
var/etext = "[e]"
|
||||
var/eid = "[e]" // Exception ID, for tracking repeated exceptions
|
||||
var/ptext = "" // "processing..." text, for what was being processed (if known)
|
||||
if(istype(e))
|
||||
etext += " in [e.file], line [e.line]"
|
||||
eid = "[e.file]:[e.line]"
|
||||
if(eid in exceptions)
|
||||
if(exceptions[eid]++ >= 10)
|
||||
return
|
||||
else
|
||||
exceptions[eid] = 1
|
||||
if(istype(thrower, /datum))
|
||||
var/datum/D = thrower
|
||||
ptext = " processing [D.type]"
|
||||
if(istype(thrower, /atom))
|
||||
var/atom/A = thrower
|
||||
ptext += " ([A]) ([A.x],[A.y],[A.z])"
|
||||
log_to_dd("\[[time_stamp()]\] Process [name] caught exception[ptext]: [etext]")
|
||||
if(exceptions[eid] >= 10)
|
||||
log_to_dd("This exception will now be ignored for ten minutes.")
|
||||
spawn(6000)
|
||||
exceptions[eid] = 0
|
||||
|
||||
/datum/controller/process/proc/catchBadType(var/datum/caught)
|
||||
if(isnull(caught) || !istype(caught) || !isnull(caught.gcDestroyed))
|
||||
return // Only bother with types we can identify and that don't belong
|
||||
catchException("Type [caught.type] does not belong in process' queue")
|
||||
@@ -17,7 +17,10 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
// Process name -> process object map
|
||||
var/tmp/datum/controller/process/list/nameToProcessMap = new
|
||||
|
||||
// Process last start times
|
||||
// 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
|
||||
@@ -29,8 +32,8 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
// 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
|
||||
// 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
|
||||
@@ -38,6 +41,25 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
// 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
|
||||
@@ -57,7 +79,7 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
|
||||
var/process
|
||||
// Add all the processes we can find, except for the ticker
|
||||
for (process in typesof(/datum/controller/process) - /datum/controller/process)
|
||||
for (process in subtypesof(/datum/controller/process))
|
||||
if (!(process in deferredSetupList))
|
||||
addProcess(new process(src))
|
||||
|
||||
@@ -66,11 +88,22 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
|
||||
/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()
|
||||
@@ -92,15 +125,11 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
// 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)].")
|
||||
message_admins("Process '[p.name]' may be hung.")
|
||||
if(PROCESS_STATUS_HUNG)
|
||||
message_admins("Process '[p.name]' is [p.getStatusText(status)].")
|
||||
p.handleHung()
|
||||
message_admins("Process '[p.name]' is hung and will be restarted.")
|
||||
|
||||
/datum/controller/processScheduler/proc/queueProcesses()
|
||||
for(var/datum/controller/process/p in processes)
|
||||
@@ -108,12 +137,8 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
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)
|
||||
if (world.time >= last_queued[p] + p.schedule_interval)
|
||||
setQueuedProcessState(p)
|
||||
|
||||
/datum/controller/processScheduler/proc/runQueuedProcesses()
|
||||
@@ -176,6 +201,10 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
|
||||
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)
|
||||
@@ -197,8 +226,6 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
if (!(process in idle))
|
||||
idle += process
|
||||
|
||||
process.idle()
|
||||
|
||||
/datum/controller/processScheduler/proc/setQueuedProcessState(var/datum/controller/process/process)
|
||||
if (process in running)
|
||||
running -= process
|
||||
@@ -218,21 +245,22 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
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
|
||||
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 = world.timeofday
|
||||
time = TimeOfHour
|
||||
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if (time < last_start[process])
|
||||
last_start[process] -= 864000
|
||||
last_start[process] -= 36000
|
||||
|
||||
var/lastRunTime = time - last_start[process]
|
||||
|
||||
@@ -273,6 +301,12 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
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
|
||||
|
||||
@@ -310,11 +344,39 @@ var/global/datum/controller/processScheduler/processScheduler
|
||||
var/datum/controller/process/process = nameToProcessMap[processName]
|
||||
process.disable()
|
||||
|
||||
/datum/controller/processScheduler/proc/getProcess(var/name)
|
||||
return nameToProcessMap[name]
|
||||
/datum/controller/processScheduler/proc/getCurrentTickElapsedTime()
|
||||
if (world.time > currentTick)
|
||||
updateCurrentTickData()
|
||||
return 0
|
||||
else
|
||||
return TimeOfHour - currentTickStart
|
||||
|
||||
/datum/controller/processScheduler/proc/getProcessLastRunTime(var/datum/controller/process/process)
|
||||
return last_run_time[process]
|
||||
/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/getIsRunning()
|
||||
return isRunning
|
||||
/datum/controller/processScheduler/proc/updateTimeAllowance()
|
||||
// Time allowance goes down linearly with world.cpu.
|
||||
var/tmp/error = cpuAverage - 100
|
||||
var/tmp/timeAllowanceDelta = sign(error) * -0.5 * world.tick_lag * max(0, 0.001 * abs(error))
|
||||
|
||||
//timeAllowance = world.tick_lag * min(1, 0.5 * ((200/max(1,cpuAverage)) - 1))
|
||||
timeAllowance = min(timeAllowanceMax, max(0, timeAllowance + timeAllowanceDelta))
|
||||
|
||||
/datum/controller/processScheduler/proc/sign(var/x)
|
||||
if (x == 0)
|
||||
return 1
|
||||
return x / abs(x)
|
||||
|
||||
/datum/controller/processScheduler/proc/statProcesses()
|
||||
if(!isRunning)
|
||||
stat("Processes", "Scheduler not running")
|
||||
return
|
||||
stat("Processes", "[processes.len] (R [running.len] / Q [queued.len] / I [idle.len])")
|
||||
stat(null, "[round(cpuAverage, 0.1)] CPU, [round(timeAllowance, 0.1)/10] TA")
|
||||
for(var/datum/controller/process/p in processes)
|
||||
p.statProcess()
|
||||
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* updateQueue.dm
|
||||
*/
|
||||
|
||||
#ifdef UPDATE_QUEUE_DEBUG
|
||||
#define uq_dbg(text) world << text
|
||||
#else
|
||||
#define uq_dbg(text)
|
||||
#endif
|
||||
/datum/updateQueue
|
||||
var/tmp/list/objects
|
||||
var/tmp/previousStart
|
||||
var/tmp/procName
|
||||
var/tmp/list/arguments
|
||||
var/tmp/datum/updateQueueWorker/currentWorker
|
||||
var/tmp/workerTimeout
|
||||
var/tmp/adjustedWorkerTimeout
|
||||
var/tmp/currentKillCount
|
||||
var/tmp/totalKillCount
|
||||
|
||||
/datum/updateQueue/New(list/objects = list(), procName = "update", list/arguments = list(), workerTimeout = 2, inplace = 0)
|
||||
..()
|
||||
|
||||
uq_dbg("Update queue created.")
|
||||
|
||||
// Init proc allows for recycling the worker.
|
||||
init(objects = objects, procName = procName, arguments = arguments, workerTimeout = workerTimeout, inplace = inplace)
|
||||
|
||||
/**
|
||||
* init
|
||||
* @param list objects objects to update
|
||||
* @param text procName the proc to call on each item in the object list
|
||||
* @param list arguments optional arguments to pass to the update proc
|
||||
* @param number workerTimeout number of ticks to wait for an update to
|
||||
finish before forking a new update worker
|
||||
* @param bool inplace whether the updateQueue should make a copy of objects.
|
||||
the internal list will be modified, so it is usually
|
||||
a good idea to leave this alone. Default behavior is to
|
||||
copy.
|
||||
*/
|
||||
/datum/updateQueue/proc/init(list/objects = list(), procName = "update", list/arguments = list(), workerTimeout = 2, inplace = 0)
|
||||
uq_dbg("Update queue initialization started.")
|
||||
|
||||
if (!inplace)
|
||||
// Make an internal copy of the list so we're not modifying the original.
|
||||
initList(objects)
|
||||
else
|
||||
src.objects = objects
|
||||
|
||||
// Init vars
|
||||
src.procName = procName
|
||||
src.arguments = arguments
|
||||
src.workerTimeout = workerTimeout
|
||||
|
||||
adjustedWorkerTimeout = workerTimeout
|
||||
currentKillCount = 0
|
||||
totalKillCount = 0
|
||||
|
||||
uq_dbg("Update queue initialization finished. procName = '[procName]'")
|
||||
|
||||
/datum/updateQueue/proc/initList(list/toCopy)
|
||||
/**
|
||||
* We will copy the list in reverse order, as our doWork proc
|
||||
* will access them by popping an element off the end of the list.
|
||||
* This ends up being quite a lot faster than taking elements off
|
||||
* the head of the list.
|
||||
*/
|
||||
objects = new
|
||||
|
||||
uq_dbg("Copying [toCopy.len] items for processing.")
|
||||
|
||||
for(var/i=toCopy.len,i>0,)
|
||||
objects.len++
|
||||
objects[objects.len] = toCopy[i--]
|
||||
|
||||
/datum/updateQueue/proc/Run()
|
||||
uq_dbg("Starting run...")
|
||||
|
||||
startWorker()
|
||||
while (istype(currentWorker) && !currentWorker.finished)
|
||||
sleep(2)
|
||||
checkWorker()
|
||||
|
||||
uq_dbg("UpdateQueue completed run.")
|
||||
|
||||
/datum/updateQueue/proc/checkWorker()
|
||||
if(istype(currentWorker))
|
||||
// If world.timeofday has rolled over, then we need to adjust.
|
||||
if(world.timeofday < currentWorker.lastStart)
|
||||
currentWorker.lastStart -= 864000
|
||||
|
||||
if(world.timeofday - currentWorker.lastStart > adjustedWorkerTimeout)
|
||||
// This worker is a bit slow, let's spawn a new one and kill the old one.
|
||||
uq_dbg("Current worker is lagging... starting a new one.")
|
||||
killWorker()
|
||||
startWorker()
|
||||
else // No worker!
|
||||
uq_dbg("update queue ended up without a worker... starting a new one...")
|
||||
startWorker()
|
||||
|
||||
/datum/updateQueue/proc/startWorker()
|
||||
// only run the worker if we have objects to work on
|
||||
if(objects.len)
|
||||
uq_dbg("Starting worker process.")
|
||||
|
||||
// No need to create a fresh worker if we already have one...
|
||||
if (istype(currentWorker))
|
||||
currentWorker.init(objects, procName, arguments)
|
||||
else
|
||||
currentWorker = new(objects, procName, arguments)
|
||||
currentWorker.start()
|
||||
else
|
||||
uq_dbg("Queue is empty. No worker was started.")
|
||||
currentWorker = null
|
||||
|
||||
/datum/updateQueue/proc/killWorker()
|
||||
// Kill the worker
|
||||
currentWorker.kill()
|
||||
currentWorker = null
|
||||
// After we kill a worker, yield so that if the worker's been tying up the cpu, other stuff can immediately resume
|
||||
sleep(-1)
|
||||
currentKillCount++
|
||||
totalKillCount++
|
||||
if (currentKillCount >= 3)
|
||||
uq_dbg("[currentKillCount] workers have been killed with a timeout of [adjustedWorkerTimeout]. Increasing worker timeout to compensate.")
|
||||
adjustedWorkerTimeout++
|
||||
currentKillCount = 0
|
||||
@@ -1,83 +0,0 @@
|
||||
datum/updateQueueWorker
|
||||
var/tmp/list/objects
|
||||
var/tmp/killed
|
||||
var/tmp/finished
|
||||
var/tmp/procName
|
||||
var/tmp/list/arguments
|
||||
var/tmp/lastStart
|
||||
var/tmp/cpuThreshold
|
||||
|
||||
datum/updateQueueWorker/New(var/list/objects, var/procName, var/list/arguments, var/cpuThreshold = 90)
|
||||
..()
|
||||
uq_dbg("updateQueueWorker created.")
|
||||
|
||||
init(objects, procName, arguments, cpuThreshold)
|
||||
|
||||
datum/updateQueueWorker/proc/init(var/list/objects, var/procName, var/list/arguments, var/cpuThreshold = 90)
|
||||
src.objects = objects
|
||||
src.procName = procName
|
||||
src.arguments = arguments
|
||||
src.cpuThreshold = cpuThreshold
|
||||
|
||||
killed = 0
|
||||
finished = 0
|
||||
|
||||
datum/updateQueueWorker/proc/doWork()
|
||||
// If there's nothing left to execute or we were killed, mark finished and return.
|
||||
if (!objects || !objects.len) return finished()
|
||||
|
||||
lastStart = world.timeofday // Absolute number of ticks since the world started up
|
||||
|
||||
var/datum/object = objects[objects.len] // Pull out the object
|
||||
objects.len-- // Remove the object from the list
|
||||
|
||||
if (istype(object) && !isturf(object) && !object.disposed && isnull(object.gcDestroyed)) // We only work with real objects
|
||||
call(object, procName)(arglist(arguments))
|
||||
|
||||
// If there's nothing left to execute
|
||||
// or we were killed while running the above code, mark finished and return.
|
||||
if (!objects || !objects.len) return finished()
|
||||
|
||||
if (world.cpu > cpuThreshold)
|
||||
// We don't want to force a tick into overtime!
|
||||
// If the tick is about to go overtime, spawn the next update to go
|
||||
// in the next tick.
|
||||
uq_dbg("tick went into overtime with world.cpu = [world.cpu], deferred next update to next tick [1+(world.time / world.tick_lag)]")
|
||||
|
||||
spawn(1)
|
||||
doWork()
|
||||
else
|
||||
spawn(0) // Execute anonymous function immediately as if we were in a while loop...
|
||||
doWork()
|
||||
|
||||
datum/updateQueueWorker/proc/finished()
|
||||
uq_dbg("updateQueueWorker finished.")
|
||||
/**
|
||||
* If the worker was killed while it was working on something, it
|
||||
* should delete itself when it finally finishes working on it.
|
||||
* Meanwhile, the updateQueue will have proceeded on with the rest of
|
||||
* the queue. This will also terminate the spawned function that was
|
||||
* created in the kill() proc.
|
||||
*/
|
||||
if(killed)
|
||||
del(src)
|
||||
|
||||
finished = 1
|
||||
|
||||
datum/updateQueueWorker/proc/kill()
|
||||
uq_dbg("updateQueueWorker killed.")
|
||||
killed = 1
|
||||
objects = null
|
||||
|
||||
/**
|
||||
* If the worker is not done in 30 seconds after it's killed,
|
||||
* we'll forcibly delete it, causing the anonymous function it was
|
||||
* running to be terminated. Hasta la vista, baby.
|
||||
*/
|
||||
spawn(300)
|
||||
del(src)
|
||||
|
||||
datum/updateQueueWorker/proc/start()
|
||||
uq_dbg("updateQueueWorker started.")
|
||||
spawn(0)
|
||||
doWork()
|
||||
@@ -1,94 +0,0 @@
|
||||
/datum/processSchedulerView
|
||||
|
||||
/datum/processSchedulerView/Topic(href, href_list)
|
||||
if (!href_list["action"])
|
||||
return
|
||||
|
||||
switch (href_list["action"])
|
||||
if ("kill")
|
||||
var/toKill = href_list["name"]
|
||||
processScheduler.killProcess(toKill)
|
||||
refreshProcessTable()
|
||||
if ("enable")
|
||||
var/toEnable = href_list["name"]
|
||||
processScheduler.enableProcess(toEnable)
|
||||
refreshProcessTable()
|
||||
if ("disable")
|
||||
var/toDisable = href_list["name"]
|
||||
processScheduler.disableProcess(toDisable)
|
||||
refreshProcessTable()
|
||||
if ("refresh")
|
||||
refreshProcessTable()
|
||||
|
||||
/datum/processSchedulerView/proc/refreshProcessTable()
|
||||
windowCall("handleRefresh", getProcessTable())
|
||||
|
||||
/datum/processSchedulerView/proc/windowCall(var/function, var/data = null)
|
||||
usr << output(data, "processSchedulerContext.browser:[function]")
|
||||
|
||||
/datum/processSchedulerView/proc/getProcessTable()
|
||||
var/text = "<table class=\"table table-striped\"><thead><tr><td>Name</td><td>Avg(s)</td><td>Last(s)</td><td>Highest(s)</td><td>Tickcount</td><td>Tickrate</td><td>State</td><td>Action</td></tr></thead><tbody>"
|
||||
// and the context of each
|
||||
for (var/list/data in processScheduler.getStatusData())
|
||||
text += "<tr>"
|
||||
text += "<td>[data["name"]]</td>"
|
||||
text += "<td>[num2text(data["averageRunTime"]/10,3)]</td>"
|
||||
text += "<td>[num2text(data["lastRunTime"]/10,3)]</td>"
|
||||
text += "<td>[num2text(data["highestRunTime"]/10,3)]</td>"
|
||||
text += "<td>[num2text(data["ticks"],4)]</td>"
|
||||
text += "<td>[data["schedule"]]</td>"
|
||||
text += "<td>[data["status"]]</td>"
|
||||
text += "<td><button class=\"btn kill-btn\" data-process-name=\"[data["name"]]\" id=\"kill-[data["name"]]\">Kill</button>"
|
||||
if (data["disabled"])
|
||||
text += "<button class=\"btn enable-btn\" data-process-name=\"[data["name"]]\" id=\"enable-[data["name"]]\">Enable</button>"
|
||||
else
|
||||
text += "<button class=\"btn disable-btn\" data-process-name=\"[data["name"]]\" id=\"disable-[data["name"]]\">Disable</button>"
|
||||
text += "</td>"
|
||||
text += "</tr>"
|
||||
|
||||
text += "</tbody></table>"
|
||||
return text
|
||||
|
||||
/**
|
||||
* getContext
|
||||
* Outputs an interface showing stats for all processes.
|
||||
*/
|
||||
/datum/processSchedulerView/proc/getContext()
|
||||
bootstrap_browse()
|
||||
usr << browse('processScheduler.js', "file=processScheduler.js;display=0")
|
||||
|
||||
var/text = {"<html><head>
|
||||
<title>Process Scheduler Detail</title>
|
||||
<script type="text/javascript">var ref = '\ref[src]';</script>
|
||||
[bootstrap_includes()]
|
||||
<script type="text/javascript" src="processScheduler.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Process Scheduler</h2>
|
||||
<div class="btn-group">
|
||||
<button id="btn-refresh" class="btn">Refresh</button>
|
||||
</div>
|
||||
|
||||
<h3>The process scheduler controls [processScheduler.getProcessCount()] loops.<h3>"}
|
||||
|
||||
text += "<div id=\"processTable\">"
|
||||
text += getProcessTable()
|
||||
text += "</div></body></html>"
|
||||
|
||||
usr << browse(text, "window=processSchedulerContext;size=800x600")
|
||||
|
||||
/datum/processSchedulerView/proc/bootstrap_browse()
|
||||
usr << browse('bower_components/jquery/dist/jquery.min.js', "file=jquery.min.js;display=0")
|
||||
usr << browse('bower_components/bootstrap2.3.2/bootstrap/js/bootstrap.min.js', "file=bootstrap.min.js;display=0")
|
||||
usr << browse('bower_components/bootstrap2.3.2/bootstrap/css/bootstrap.min.css', "file=bootstrap.min.css;display=0")
|
||||
usr << browse('bower_components/bootstrap2.3.2/bootstrap/img/glyphicons-halflings-white.png', "file=glyphicons-halflings-white.png;display=0")
|
||||
usr << browse('bower_components/bootstrap2.3.2/bootstrap/img/glyphicons-halflings.png', "file=glyphicons-halflings.png;display=0")
|
||||
usr << browse('bower_components/json2/json2.js', "file=json2.js;display=0")
|
||||
|
||||
/datum/processSchedulerView/proc/bootstrap_includes()
|
||||
return {"
|
||||
<link rel="stylesheet" href="bootstrap.min.css" />
|
||||
<script type="text/javascript" src="json2.js"></script>
|
||||
<script type="text/javascript" src="jquery.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap.js"></script>
|
||||
"}
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* testDyingUpdateQueueProcess
|
||||
* This process is an example of a process using an updateQueue.
|
||||
* The datums updated by this process behave badly and block the update loop
|
||||
* by sleeping. If you #define UPDATE_QUEUE_DEBUG, you will see the updateQueue
|
||||
* killing off its worker processes and spawning new ones to work around slow
|
||||
* updates. This means that if you have a code path that sleeps for a long time
|
||||
* in mob.Life once in a blue moon, the mob update loop will not hang.
|
||||
*/
|
||||
/datum/slowTestDatum/proc/wackyUpdateProcessName()
|
||||
sleep(rand(0,20)) // Randomly REALLY slow :|
|
||||
|
||||
/datum/controller/process/testDyingUpdateQueueProcess
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
var/tmp/list/testDatums = list()
|
||||
|
||||
/datum/controller/process/testDyingUpdateQueueProcess/setup()
|
||||
name = "Dying UpdateQueue Process"
|
||||
schedule_interval = 30 // every 3 seconds
|
||||
updateQueueInstance = new
|
||||
for(var/i = 1, i < 30, i++)
|
||||
testDatums.Add(new /datum/slowTestDatum)
|
||||
|
||||
/datum/controller/process/testDyingUpdateQueueProcess/doWork()
|
||||
updateQueueInstance.init(testDatums, "wackyUpdateProcessName")
|
||||
updateQueueInstance.Run()
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
These are simple defaults for your project.
|
||||
*/
|
||||
#define DEBUG
|
||||
|
||||
var/global/datum/processSchedulerView/processSchedulerView
|
||||
|
||||
world
|
||||
loop_checks = 0
|
||||
New()
|
||||
..()
|
||||
processScheduler = new
|
||||
processSchedulerView = new
|
||||
|
||||
mob
|
||||
step_size = 8
|
||||
|
||||
New()
|
||||
..()
|
||||
|
||||
|
||||
verb
|
||||
startProcessScheduler()
|
||||
set name = "Start Process Scheduler"
|
||||
processScheduler.setup()
|
||||
processScheduler.start()
|
||||
|
||||
getProcessSchedulerContext()
|
||||
set name = "Get Process Scheduler Status Panel"
|
||||
processSchedulerView.getContext()
|
||||
|
||||
runUpdateQueueTests()
|
||||
set name = "Run Update Queue Testsuite"
|
||||
var/datum/updateQueueTests/t = new
|
||||
t.runTests()
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* testHungProcess
|
||||
* This process is an example of a simple update loop process that hangs.
|
||||
*/
|
||||
|
||||
/datum/controller/process/testHungProcess/setup()
|
||||
name = "Hung Process"
|
||||
schedule_interval = 30 // every 3 seconds
|
||||
|
||||
/datum/controller/process/testHungProcess/doWork()
|
||||
sleep(1000) // FUCK
|
||||
// scheck is also responsible for handling hung processes. If a process
|
||||
// hangs, and later resumes, but has already been killed by the scheduler,
|
||||
// scheck will force the process to bail out.
|
||||
scheck()
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* testNiceProcess
|
||||
* This process is an example of a simple update loop process that is
|
||||
* relatively fast.
|
||||
*/
|
||||
|
||||
/datum/controller/process/testNiceProcess/setup()
|
||||
name = "Nice Process"
|
||||
schedule_interval = 10 // every second
|
||||
|
||||
/datum/controller/process/testNiceProcess/doWork()
|
||||
sleep(rand(1,5)) // Just to pretend we're doing something
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* testSlowProcess
|
||||
* This process is an example of a simple update loop process that is slow.
|
||||
* The update loop here sleeps inside to provide an example, but if you had
|
||||
* a computationally intensive loop process that is simply slow, you can use
|
||||
* scheck() inside the loop to force it to yield periodically according to
|
||||
* the sleep_interval var. By default, scheck will cause a loop to sleep every
|
||||
* 2 ticks.
|
||||
*/
|
||||
|
||||
/datum/controller/process/testSlowProcess/setup()
|
||||
name = "Slow Process"
|
||||
schedule_interval = 30 // every 3 seconds
|
||||
|
||||
/datum/controller/process/testSlowProcess/doWork()
|
||||
// set background = 1 will cause loop constructs to sleep periodically,
|
||||
// whenever the BYOND scheduler deems it productive to do so.
|
||||
// This behavior is not always sufficient, nor is it always consistent.
|
||||
// Rather than leaving it up to the BYOND scheduler, we can control it
|
||||
// ourselves and leave nothing to the black box.
|
||||
set background = 1
|
||||
|
||||
for(var/i=1,i<30,i++)
|
||||
// Just to pretend we're doing something here
|
||||
sleep(rand(3, 5))
|
||||
|
||||
// Forces this loop to yield(sleep) periodically.
|
||||
scheck()
|
||||
@@ -1,209 +0,0 @@
|
||||
var/global/list/updateQueueTestCount = list()
|
||||
|
||||
/datum/updateQueueTests
|
||||
var/start
|
||||
proc
|
||||
runTests()
|
||||
world << "<b>Running 9 tests...</b>"
|
||||
testUpdateQueuePerformance()
|
||||
sleep(1)
|
||||
testInplace()
|
||||
sleep(1)
|
||||
testInplaceUpdateQueuePerformance()
|
||||
sleep(1)
|
||||
testUpdateQueueReinit()
|
||||
sleep(1)
|
||||
testCrashingQueue()
|
||||
sleep(1)
|
||||
testEmptyQueue()
|
||||
sleep(1)
|
||||
testManySlowItemsInQueue()
|
||||
sleep(1)
|
||||
testVariableWorkerTimeout()
|
||||
sleep(1)
|
||||
testReallySlowItemInQueue()
|
||||
sleep(1)
|
||||
world << "<b>Finished!</b>"
|
||||
|
||||
beginTiming()
|
||||
start = world.time
|
||||
|
||||
endTiming(text)
|
||||
var/time = (world.time - start) / world.tick_lag
|
||||
world << {"<b><font color="blue">Performance - [text] - <font color="green">[time]</font> ticks</font></b>"}
|
||||
|
||||
getCount()
|
||||
return updateQueueTestCount[updateQueueTestCount.len]
|
||||
|
||||
incrementTestCount()
|
||||
updateQueueTestCount.len++
|
||||
updateQueueTestCount[updateQueueTestCount.len] = 0
|
||||
|
||||
assertCountEquals(count, text)
|
||||
assertThat(getCount() == count, text)
|
||||
|
||||
assertCountLessThan(count, text)
|
||||
assertThat(getCount() < count, text)
|
||||
|
||||
assertCountGreaterThan(count, text)
|
||||
assertThat(getCount() > count, text)
|
||||
|
||||
assertThat(condition, text)
|
||||
if (condition)
|
||||
world << {"<font color="green"><b>PASS</b></font>: [text]"}
|
||||
else
|
||||
world << {"<b><font color="red">FAIL</font>: [text]</b>"}
|
||||
|
||||
testUpdateQueuePerformance()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=100000,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
|
||||
beginTiming()
|
||||
uq.Run()
|
||||
endTiming("updating 100000 simple objects")
|
||||
|
||||
assertCountEquals(100000, "test that update queue updates all objects expected")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testUpdateQueueReinit()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=100,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
objs = new
|
||||
|
||||
for(var/i=1,i<=100,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
uq.init(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(200, "test that update queue reinitializes properly and updates all objects as expected.")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testInplace()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=100,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
var/datum/updateQueue/uq = new(objects = objs, inplace = 1)
|
||||
uq.Run()
|
||||
assertThat(objs.len == 0, "test that update queue inplace option really works inplace")
|
||||
assertCountEquals(100, "test that inplace update queue updates the right number of objects")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testInplaceUpdateQueuePerformance()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=100000,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
|
||||
beginTiming()
|
||||
uq.Run()
|
||||
endTiming("updating 100000 simple objects in place")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testCrashingQueue()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=10,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
objs.Add(new /datum/uqTestDatum/crasher(updateQueueTestCount.len))
|
||||
for(var/i=1,i<=10,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(20, "test that update queue handles crashed update procs OK")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testEmptyQueue()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(0, "test that update queue doesn't barf on empty lists")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testManySlowItemsInQueue()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=30,i++)
|
||||
objs.Add(new /datum/uqTestDatum/slow(updateQueueTestCount.len))
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(30, "test that update queue slows down execution if too many objects are slow to update")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testVariableWorkerTimeout()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=20,i++)
|
||||
objs.Add(new /datum/uqTestDatum/slow(updateQueueTestCount.len))
|
||||
var/datum/updateQueue/uq = new(objs, workerTimeout=6)
|
||||
uq.Run()
|
||||
assertCountEquals(20, "test that variable worker timeout works properly")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
testReallySlowItemInQueue()
|
||||
incrementTestCount()
|
||||
var/list/objs = new
|
||||
for(var/i=1,i<=10,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
objs.Add(new /datum/uqTestDatum/reallySlow(updateQueueTestCount.len))
|
||||
for(var/i=1,i<=10,i++)
|
||||
objs.Add(new /datum/uqTestDatum/fast(updateQueueTestCount.len))
|
||||
var/datum/updateQueue/uq = new(objs)
|
||||
uq.Run()
|
||||
assertCountEquals(20, "test that update queue skips objects that are too slow to update")
|
||||
del(objs)
|
||||
del(uq)
|
||||
|
||||
|
||||
|
||||
datum/uqTestDatum
|
||||
var/testNum
|
||||
New(testNum)
|
||||
..()
|
||||
src.testNum = testNum
|
||||
proc/update()
|
||||
updateQueueTestCount[testNum]++
|
||||
proc/lag(cycles)
|
||||
set background = 1
|
||||
for(var/i=0,i<cycles,)
|
||||
i++
|
||||
datum/uqTestDatum/fast
|
||||
|
||||
datum/uqTestDatum/slow
|
||||
update()
|
||||
set background = 1
|
||||
var/start = world.timeofday
|
||||
while(world.timeofday - start < 5) // lag 4 deciseconds
|
||||
..()
|
||||
|
||||
datum/uqTestDatum/reallySlow
|
||||
update()
|
||||
set background = 1
|
||||
var/start = world.timeofday
|
||||
while(world.timeofday - start < 300) // lag 30 seconds
|
||||
..()
|
||||
|
||||
datum/uqTestDatum/crasher
|
||||
update()
|
||||
CRASH("I crashed! (I am supposed to crash XD)")
|
||||
..() // This should do nothing lol
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* testUpdateQueueProcess
|
||||
* This process is an example of a process using an updateQueue.
|
||||
* The datums updated by this process behave nicely and do not block.
|
||||
*/
|
||||
|
||||
/datum/fastTestDatum/proc/wackyUpdateProcessName()
|
||||
sleep(prob(10)) // Pretty quick, usually instant
|
||||
|
||||
/datum/controller/process/testUpdateQueueProcess
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
var/tmp/list/testDatums = list()
|
||||
|
||||
/datum/controller/process/testUpdateQueueProcess/setup()
|
||||
name = "UpdateQueue Process"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
updateQueueInstance = new
|
||||
for(var/i = 1, i < 30, i++)
|
||||
testDatums.Add(new /datum/fastTestDatum)
|
||||
|
||||
/datum/controller/process/testUpdateQueueProcess/doWork()
|
||||
updateQueueInstance.init(testDatums, "wackyUpdateProcessName")
|
||||
updateQueueInstance.Run()
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* testBadZombieProcess
|
||||
* This process is an example of a simple update loop process that hangs.
|
||||
*/
|
||||
|
||||
/datum/controller/process/testZombieProcess/setup()
|
||||
name = "Zombie Process"
|
||||
schedule_interval = 30 // every 3 seconds
|
||||
|
||||
/datum/controller/process/testZombieProcess/doWork()
|
||||
for (var/i = 0, i < 1000, i++)
|
||||
sleep(1)
|
||||
scheck()
|
||||
@@ -1,6 +1,7 @@
|
||||
/datum/controller/process/air/setup()
|
||||
name = "air"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
start_delay = 4
|
||||
|
||||
if(!air_master)
|
||||
air_master = new
|
||||
|
||||
@@ -1,10 +1,41 @@
|
||||
|
||||
// We manually initialize the alarm handlers instead of looping over all existing types
|
||||
// to make it possible to write: camera.triggerAlarm() rather than alarm_manager.managers[datum/alarm_handler/camera].triggerAlarm() or a variant thereof.
|
||||
/var/global/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
|
||||
/var/global/datum/alarm_handler/camera/camera_alarm = new()
|
||||
/var/global/datum/alarm_handler/fire/fire_alarm = new()
|
||||
/var/global/datum/alarm_handler/motion/motion_alarm = new()
|
||||
/var/global/datum/alarm_handler/power/power_alarm = new()
|
||||
|
||||
// Alarm Manager, the manager for alarms.
|
||||
var/datum/controller/process/alarm/alarm_manager
|
||||
|
||||
/datum/controller/process/alarm
|
||||
var/list/datum/alarm/all_handlers
|
||||
|
||||
/datum/controller/process/alarm/setup()
|
||||
name = "alarm"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
all_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, motion_alarm, power_alarm)
|
||||
alarm_manager = src
|
||||
|
||||
/datum/controller/process/alarm/doWork()
|
||||
alarm_manager.fire()
|
||||
for(var/datum/alarm_handler/AH in all_handlers)
|
||||
AH.process()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/alarm/getStatName()
|
||||
var/list/alarms = alarm_manager.active_alarms()
|
||||
return ..()+"([alarms.len])"
|
||||
/datum/controller/process/alarm/proc/active_alarms()
|
||||
var/list/all_alarms = new
|
||||
for(var/datum/alarm_handler/AH in all_handlers)
|
||||
var/list/alarms = AH.alarms
|
||||
all_alarms += alarms
|
||||
|
||||
return all_alarms
|
||||
|
||||
/datum/controller/process/alarm/proc/number_of_active_alarms()
|
||||
var/list/alarms = active_alarms()
|
||||
return alarms.len
|
||||
|
||||
/datum/controller/process/alarm/statProcess()
|
||||
..()
|
||||
stat(null, "[number_of_active_alarms()] alarm\s")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
var/datum/controller/process/chemistry/chemistryProcess
|
||||
|
||||
/datum/controller/process/chemistry
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
var/list/active_holders
|
||||
var/list/chemical_reactions
|
||||
var/list/chemical_reagents
|
||||
@@ -9,23 +8,23 @@ var/datum/controller/process/chemistry/chemistryProcess
|
||||
/datum/controller/process/chemistry/setup()
|
||||
name = "chemistry"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
updateQueueInstance = new
|
||||
chemistryProcess = src
|
||||
active_holders = list()
|
||||
chemical_reactions = chemical_reactions_list
|
||||
chemical_reagents = chemical_reagents_list
|
||||
|
||||
/datum/controller/process/chemistry/getStatName()
|
||||
return ..()+"([active_holders.len])"
|
||||
/datum/controller/process/chemistry/statProcess()
|
||||
..()
|
||||
stat(null, "[active_holders.len] reagent holder\s")
|
||||
|
||||
/datum/controller/process/chemistry/doWork()
|
||||
for(var/datum/reagents/holder in active_holders)
|
||||
if(!holder.process_reactions())
|
||||
active_holders -= holder
|
||||
scheck()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/chemistry/proc/mark_for_update(var/datum/reagents/holder)
|
||||
if(holder in active_holders)
|
||||
if(holder in active_holders)
|
||||
return
|
||||
|
||||
//Process once, right away. If we still need to continue then add to the active_holders list and continue later
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/datum/controller/process/disease
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
|
||||
/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()
|
||||
|
||||
/datum/controller/process/disease/getStatName()
|
||||
return ..()+"([active_diseases.len])"
|
||||
@@ -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()
|
||||
@@ -58,7 +64,7 @@ world/loop_checks = 0
|
||||
if(A.loc != null)
|
||||
testing("GC: [A] | [A.type] is located in [A.loc] instead of null")
|
||||
if(A.contents.len)
|
||||
testing("GC: [A] | [A.type] has contents: [jointext(A.contents)]")
|
||||
testing("GC: [A] | [A.type] has contents: [list2text(A.contents)]")
|
||||
if(searching.len)
|
||||
for(var/atom/D in world)
|
||||
LookForRefs(D, searching)
|
||||
@@ -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
|
||||
@@ -263,4 +281,4 @@ world/loop_checks = 0
|
||||
|
||||
#ifdef GC_FINDREF
|
||||
#undef GC_FINDREF
|
||||
#endif
|
||||
#endif
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/datum/controller/process/machinery/setup()
|
||||
name = "machinery"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
start_delay = 12
|
||||
|
||||
/datum/controller/process/machinery/doWork()
|
||||
internal_sort()
|
||||
@@ -19,10 +20,6 @@
|
||||
/datum/controller/process/machinery/proc/internal_process_machinery()
|
||||
for(var/obj/machinery/M in machines)
|
||||
if(M && !M.gcDestroyed)
|
||||
#ifdef PROFILE_MACHINES
|
||||
var/time_start = world.timeofday
|
||||
#endif
|
||||
|
||||
if(M.process() == PROCESS_KILL)
|
||||
//M.inMachineList = 0 We don't use this debugging function
|
||||
machines.Remove(M)
|
||||
@@ -31,22 +28,13 @@
|
||||
if(M && M.use_power)
|
||||
M.auto_use_power()
|
||||
|
||||
#ifdef PROFILE_MACHINES
|
||||
var/time_end = world.timeofday
|
||||
|
||||
if(!(M.type in machine_profiling))
|
||||
machine_profiling[M.type] = 0
|
||||
|
||||
machine_profiling[M.type] += (time_end - time_start)
|
||||
#endif
|
||||
|
||||
scheck()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/machinery/proc/internal_process_power()
|
||||
for(var/datum/powernet/powerNetwork in powernets)
|
||||
if(istype(powerNetwork) && !powerNetwork.disposed)
|
||||
if(istype(powerNetwork) && isnull(powerNetwork.gcDestroyed))
|
||||
powerNetwork.reset()
|
||||
scheck()
|
||||
SCHECK
|
||||
continue
|
||||
|
||||
powernets.Remove(powerNetwork)
|
||||
@@ -56,16 +44,20 @@
|
||||
for(var/obj/item/I in processing_power_items)
|
||||
if(!I.pwr_drain()) // 0 = Process Kill, remove from processing list.
|
||||
processing_power_items.Remove(I)
|
||||
scheck()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/machinery/proc/internal_process_pipenets()
|
||||
for(var/datum/pipe_network/pipeNetwork in pipe_networks)
|
||||
if(istype(pipeNetwork) && !pipeNetwork.disposed)
|
||||
if(istype(pipeNetwork) && isnull(pipeNetwork.gcDestroyed))
|
||||
pipeNetwork.process()
|
||||
scheck()
|
||||
SCHECK
|
||||
continue
|
||||
|
||||
pipe_networks.Remove(pipeNetwork)
|
||||
|
||||
/datum/controller/process/machinery/getStatName()
|
||||
return ..()+"(MCH:[machines.len] PWR:[powernets.len] PIP:[pipe_networks.len])"
|
||||
/datum/controller/process/machinery/statProcess()
|
||||
..()
|
||||
stat(null, "[machines.len] machines")
|
||||
stat(null, "[powernets.len] powernets")
|
||||
stat(null, "[pipe_networks.len] pipenets")
|
||||
stat(null, "[processing_power_items.len] power item\s")
|
||||
@@ -4,20 +4,26 @@
|
||||
/datum/controller/process/mob/setup()
|
||||
name = "mob"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
updateQueueInstance = new
|
||||
start_delay = 16
|
||||
|
||||
/datum/controller/process/mob/started()
|
||||
..()
|
||||
if(!updateQueueInstance)
|
||||
if(!mob_list)
|
||||
mob_list = list()
|
||||
else if(mob_list.len)
|
||||
updateQueueInstance = new
|
||||
if(!mob_list)
|
||||
mob_list = list()
|
||||
|
||||
/datum/controller/process/mob/doWork()
|
||||
if(updateQueueInstance)
|
||||
updateQueueInstance.init(mob_list, "Life")
|
||||
updateQueueInstance.Run()
|
||||
for(last_object in mob_list)
|
||||
var/mob/M = last_object
|
||||
if(isnull(M.gcDestroyed))
|
||||
try
|
||||
M.Life()
|
||||
catch(var/exception/e)
|
||||
catchException(e, M)
|
||||
SCHECK
|
||||
else
|
||||
catchBadType(M)
|
||||
mob_list -= M
|
||||
|
||||
/datum/controller/process/mob/getStatName()
|
||||
return ..()+"([mob_list.len])"
|
||||
/datum/controller/process/mob/statProcess()
|
||||
..()
|
||||
stat(null, "[mob_list.len] mobs")
|
||||
@@ -1,14 +1,19 @@
|
||||
/datum/controller/process/nanoui
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
|
||||
/datum/controller/process/nanoui/setup()
|
||||
name = "nanoui"
|
||||
schedule_interval = 10 // every 1 second
|
||||
updateQueueInstance = new
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
|
||||
/datum/controller/process/nanoui/statProcess()
|
||||
..()
|
||||
stat(null, "[nanomanager.processing_uis.len] UIs")
|
||||
|
||||
/datum/controller/process/nanoui/doWork()
|
||||
updateQueueInstance.init(nanomanager.processing_uis, "process")
|
||||
updateQueueInstance.Run()
|
||||
|
||||
/datum/controller/process/nanoui/getStatName()
|
||||
return ..()+"([nanomanager.processing_uis.len])"
|
||||
for(last_object in nanomanager.processing_uis)
|
||||
var/datum/nanoui/NUI = last_object
|
||||
if(istype(NUI) && isnull(NUI.gcDestroyed))
|
||||
try
|
||||
NUI.process()
|
||||
catch(var/exception/e)
|
||||
catchException(e, NUI)
|
||||
else
|
||||
catchBadType(NUI)
|
||||
nanomanager.processing_uis -= NUI
|
||||
@@ -1,24 +1,26 @@
|
||||
var/global/list/object_profiling = list()
|
||||
/datum/controller/process/obj
|
||||
var/tmp/datum/updateQueue/updateQueueInstance
|
||||
|
||||
/datum/controller/process/obj/setup()
|
||||
name = "obj"
|
||||
schedule_interval = 20 // every 2 seconds
|
||||
updateQueueInstance = new
|
||||
start_delay = 8
|
||||
|
||||
/datum/controller/process/obj/started()
|
||||
..()
|
||||
if(!updateQueueInstance)
|
||||
if(!processing_objects)
|
||||
processing_objects = list()
|
||||
else if(processing_objects.len)
|
||||
updateQueueInstance = new
|
||||
if(!processing_objects)
|
||||
processing_objects = list()
|
||||
|
||||
/datum/controller/process/obj/doWork()
|
||||
if(updateQueueInstance)
|
||||
updateQueueInstance.init(processing_objects, "process")
|
||||
updateQueueInstance.Run()
|
||||
for(last_object in processing_objects)
|
||||
var/datum/O = last_object
|
||||
if(isnull(O.gcDestroyed))
|
||||
try
|
||||
O:process()
|
||||
catch(var/exception/e)
|
||||
catchException(e, O)
|
||||
SCHECK
|
||||
else
|
||||
catchBadType(O)
|
||||
processing_objects -= O
|
||||
|
||||
/datum/controller/process/obj/getStatName()
|
||||
return ..()+"([processing_objects.len])"
|
||||
/datum/controller/process/obj/statProcess()
|
||||
..()
|
||||
stat(null, "[processing_objects.len] objects")
|
||||
@@ -8,7 +8,8 @@ var/global/list/turf/processing_turfs = list()
|
||||
for(var/turf/T in processing_turfs)
|
||||
if(T.process() == PROCESS_KILL)
|
||||
processing_turfs.Remove(T)
|
||||
scheck()
|
||||
SCHECK
|
||||
|
||||
/datum/controller/process/turf/getStatName()
|
||||
return ..()+"([processing_turfs.len])"
|
||||
/datum/controller/process/turf/statProcess()
|
||||
..()
|
||||
stat(null, "[processing_turfs.len] turf\s")
|
||||
@@ -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 = 0 // 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
|
||||
@@ -327,6 +328,9 @@ var/list/gamemode_cache = list()
|
||||
if ("log_pda")
|
||||
config.log_pda = 1
|
||||
|
||||
if ("log_world_output")
|
||||
config.log_world_output = 1
|
||||
|
||||
if ("log_hrefs")
|
||||
config.log_hrefs = 1
|
||||
|
||||
|
||||
@@ -1,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
|
||||
+11
-5
@@ -2,17 +2,20 @@
|
||||
* Category Collection *
|
||||
**********************/
|
||||
/datum/category_collection
|
||||
var/category_group_type // The type of categories to initialize
|
||||
var/list/datum/category_group/categories // The list of initialized categories
|
||||
var/category_group_type // Type of categories to initialize
|
||||
var/list/datum/category_group/categories // List of initialized categories
|
||||
var/list/datum/category_group/categories_by_name // Associative list of initialized categories, keyed by name
|
||||
|
||||
/datum/category_collection/New()
|
||||
..()
|
||||
categories = new()
|
||||
categories_by_name = new()
|
||||
for(var/category_type in typesof(category_group_type))
|
||||
var/datum/category_group/category = category_type
|
||||
if(initial(category.name))
|
||||
category = new category(src)
|
||||
categories += category
|
||||
categories_by_name[category.name] = category
|
||||
categories = dd_sortedObjectList(categories)
|
||||
|
||||
/datum/category_collection/Destroy()
|
||||
@@ -26,20 +29,23 @@
|
||||
******************/
|
||||
/datum/category_group
|
||||
var/name = ""
|
||||
var/category_item_type // The type of items to initialize
|
||||
var/list/datum/category_item/items // The list of initialized items
|
||||
var/datum/category_collection/collection // The collection this group belongs to
|
||||
var/category_item_type // Type of items to initialize
|
||||
var/list/datum/category_item/items // List of initialized items
|
||||
var/list/datum/category_item/items_by_name // Associative list of initialized items, by name
|
||||
var/datum/category_collection/collection // The collection this group belongs to
|
||||
|
||||
/datum/category_group/New(var/datum/category_collection/cc)
|
||||
..()
|
||||
collection = cc
|
||||
items = new()
|
||||
items_by_name = new()
|
||||
|
||||
for(var/item_type in typesof(category_item_type))
|
||||
var/datum/category_item/item = item_type
|
||||
if(initial(item.name))
|
||||
item = new item(src)
|
||||
items += item
|
||||
items_by_name[item.name] = item
|
||||
|
||||
// For whatever reason dd_insertObjectList(items, item) doesn't insert in the correct order
|
||||
// If you change this, confirm that character setup doesn't become completely unordered.
|
||||
|
||||
@@ -618,6 +618,35 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
|
||||
access = access_robotics
|
||||
group = "Engineering"
|
||||
|
||||
/datum/supply_packs/robolimbs_basic
|
||||
name = "Basic robolimb blueprints"
|
||||
contains = list(
|
||||
/obj/item/weapon/disk/limb/morpheus,
|
||||
/obj/item/weapon/disk/limb/xion
|
||||
)
|
||||
cost = 15
|
||||
containertype = /obj/structure/closet/crate/secure/gear
|
||||
containername = "Robolimb blueprints (basic)"
|
||||
access = access_robotics
|
||||
group = "Engineering"
|
||||
|
||||
/datum/supply_packs/robolimbs_adv
|
||||
name = "All robolimb blueprints"
|
||||
contains = list(
|
||||
/obj/item/weapon/disk/limb/bishop,
|
||||
/obj/item/weapon/disk/limb/hesphiastos,
|
||||
/obj/item/weapon/disk/limb/morpheus,
|
||||
/obj/item/weapon/disk/limb/veymed,
|
||||
/obj/item/weapon/disk/limb/wardtakahashi,
|
||||
/obj/item/weapon/disk/limb/xion,
|
||||
/obj/item/weapon/disk/limb/zenghu,
|
||||
)
|
||||
cost = 40
|
||||
containertype = /obj/structure/closet/crate/secure/gear
|
||||
containername = "Robolimb blueprints (adv)"
|
||||
access = access_robotics
|
||||
group = "Engineering"
|
||||
|
||||
/datum/supply_packs/phoron
|
||||
name = "Phoron assembly crate"
|
||||
contains = list(
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/datum/category_item/underwear/bottom/none
|
||||
name = "None"
|
||||
always_last = TRUE
|
||||
|
||||
/datum/category_item/underwear/bottom/briefs
|
||||
name = "Briefs"
|
||||
icon_state = "briefs"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/bottom/briefs/is_default(var/gender)
|
||||
return gender != FEMALE
|
||||
|
||||
/datum/category_item/underwear/bottom/boxers_loveheart
|
||||
name = "Boxers, Loveheart"
|
||||
icon_state = "boxers_loveheart"
|
||||
|
||||
/datum/category_item/underwear/bottom/boxers
|
||||
name = "Boxers"
|
||||
icon_state = "boxers"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/bottom/boxers_green_and_blue
|
||||
name = "Boxers, green & blue striped"
|
||||
icon_state = "boxers_green_and_blue"
|
||||
|
||||
/datum/category_item/underwear/bottom/panties
|
||||
name = "Panties"
|
||||
icon_state = "panties"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/bottom/panties/is_default(var/gender)
|
||||
return gender == FEMALE
|
||||
|
||||
/datum/category_item/underwear/bottom/lacy_thong
|
||||
name = "Lacy thong"
|
||||
icon_state = "lacy_thong"
|
||||
|
||||
/datum/category_item/underwear/bottom/lacy_thong_alt
|
||||
name = "Lacy thong, alt"
|
||||
icon_state = "lacy_thong_alt"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/bottom/panties_alt
|
||||
name = "Panties, alt"
|
||||
icon_state = "panties_alt"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/bottom/compression_shorts
|
||||
name = "Compression shorts"
|
||||
icon_state = "compression_shorts"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/bottom/thong
|
||||
name = "Thong"
|
||||
icon_state = "thong"
|
||||
has_color = TRUE
|
||||
@@ -0,0 +1,59 @@
|
||||
/datum/category_item/underwear/socks/none
|
||||
always_last = TRUE
|
||||
name = "None"
|
||||
|
||||
/datum/category_item/underwear/socks/normal
|
||||
name = "Normal"
|
||||
icon_state = "socks_norm"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/socks/short
|
||||
name = "Short"
|
||||
icon_state = "socks_short"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/socks/thigh
|
||||
name = "Thigh"
|
||||
icon_state = "socks_thigh"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/socks/knee
|
||||
name = "Knee"
|
||||
icon_state = "socks_knee"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/socks/striped_knee
|
||||
name = "Knee, striped"
|
||||
icon_state = "striped_knee"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/socks/striped_thigh
|
||||
name = "Thigh, striped"
|
||||
icon_state = "striped_thigh"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/socks/pantyhose
|
||||
name = "Pantyhose"
|
||||
icon_state = "pantyhose"
|
||||
|
||||
/datum/category_item/underwear/socks/thin_thigh
|
||||
name = "Thigh, thin"
|
||||
icon_state = "thin_thigh"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/socks/thin_knee
|
||||
name = "Knee, thin"
|
||||
icon_state = "thin_knee"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/socks/rainbow_thigh
|
||||
name = "Thigh, rainbow"
|
||||
icon_state = "rainbow_thigh"
|
||||
|
||||
/datum/category_item/underwear/socks/rainbow_knee
|
||||
name = "Knee, rainbow"
|
||||
icon_state = "rainbow_knee"
|
||||
|
||||
/datum/category_item/underwear/socks/fishnet
|
||||
name = "Fishnet"
|
||||
icon_state = "fishnet"
|
||||
@@ -0,0 +1,43 @@
|
||||
/datum/category_item/underwear/top/none
|
||||
name = "None"
|
||||
always_last = TRUE
|
||||
|
||||
/datum/category_item/underwear/top/none/is_default(var/gender)
|
||||
return gender != FEMALE
|
||||
|
||||
/datum/category_item/underwear/top/bra
|
||||
is_default = TRUE
|
||||
name = "Bra"
|
||||
icon_state = "bra"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/top/bra/is_default(var/gender)
|
||||
return gender == FEMALE
|
||||
|
||||
/datum/category_item/underwear/top/sports_bra
|
||||
name = "Sports bra"
|
||||
icon_state = "sports_bra"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/top/sports_bra_alt
|
||||
name = "Sports bra, alt"
|
||||
icon_state = "sports_bra_alt"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/top/lacy_bra
|
||||
name = "Lacy bra"
|
||||
icon_state = "lacy_bra"
|
||||
|
||||
/datum/category_item/underwear/top/lacy_bra_alt
|
||||
name = "Lacy bra, alt"
|
||||
icon_state = "lacy_bra_alt"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/top/lacy_bra_alt_stripe
|
||||
name = "Lacy bra, alt, stripe"
|
||||
icon_state = "lacy_bra_alt_stripe"
|
||||
|
||||
/datum/category_item/underwear/top/halterneck_bra
|
||||
name = "Halterneck bra"
|
||||
icon_state = "halterneck_bra"
|
||||
has_color = TRUE
|
||||
@@ -0,0 +1,65 @@
|
||||
/datum/category_item/underwear/undershirt/none
|
||||
is_default = TRUE
|
||||
name = "None"
|
||||
always_last = TRUE
|
||||
|
||||
/datum/category_item/underwear/undershirt/shirt
|
||||
name = "Shirt"
|
||||
icon_state = "undershirt"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/undershirt/tank_top
|
||||
name = "Tank top"
|
||||
icon_state = "tanktop"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/undershirt/tank_top_alt
|
||||
name = "Tank top, alt"
|
||||
icon_state = "tanktop_alt"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/undershirt/tank_top_fire
|
||||
name = "Tank top, fire"
|
||||
icon_state = "tank_fire_s"
|
||||
|
||||
/datum/category_item/underwear/undershirt/shirt_heart
|
||||
name = "Shirt, heart"
|
||||
icon_state = "lover_s"
|
||||
|
||||
/datum/category_item/underwear/undershirt/shirt_nt
|
||||
name = "Shirt, NT"
|
||||
icon_state = "shirt_nano_s"
|
||||
|
||||
/datum/category_item/underwear/undershirt/shirt_love_nt
|
||||
name = "Shirt, I<3NT"
|
||||
icon_state = "lover_s"
|
||||
|
||||
/datum/category_item/underwear/undershirt/shortsleeve_shirt
|
||||
name = "Shortsleeve shirt"
|
||||
icon_state = "shortsleeve"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/undershirt/polo_shirt
|
||||
name = "Polo shirt"
|
||||
icon_state = "polo"
|
||||
has_color = TRUE
|
||||
|
||||
/datum/category_item/underwear/undershirt/sport_shirt_green
|
||||
name = "Sport shirt, green"
|
||||
icon_state = "greenshirtsport_s"
|
||||
|
||||
/datum/category_item/underwear/undershirt/sport_shirt_red
|
||||
name = "Sport shirt, red"
|
||||
icon_state = "redshirtsport_s"
|
||||
|
||||
/datum/category_item/underwear/undershirt/sport_shirt_blue
|
||||
name = "Sport shirt, blue"
|
||||
icon_state = "blueshirtsport_s"
|
||||
|
||||
/datum/category_item/underwear/undershirt/shirt_tiedye
|
||||
name = "Shirt, tiedye"
|
||||
icon_state = "shirt_tiedye_s"
|
||||
|
||||
/datum/category_item/underwear/undershirt/shirt_blue_striped
|
||||
name = "Shirt, blue stripes"
|
||||
icon_state = "shirt_stripes_s"
|
||||
@@ -0,0 +1,66 @@
|
||||
/****************************
|
||||
* Category Collection Setup *
|
||||
****************************/
|
||||
/datum/category_collection/underwear
|
||||
category_group_type = /datum/category_group/underwear
|
||||
|
||||
/*************
|
||||
* Categories *
|
||||
*************/
|
||||
/datum/category_group/underwear
|
||||
var/sort_order // Lower sort order is applied as icons first
|
||||
|
||||
datum/category_group/underwear/dd_SortValue()
|
||||
return sort_order
|
||||
|
||||
/datum/category_group/underwear/top
|
||||
name = "Underwear, top"
|
||||
sort_order = 1
|
||||
category_item_type = /datum/category_item/underwear/top
|
||||
|
||||
/datum/category_group/underwear/bottom
|
||||
name = "Underwear, bottom"
|
||||
sort_order = 2
|
||||
category_item_type = /datum/category_item/underwear/bottom
|
||||
|
||||
/datum/category_group/underwear/socks
|
||||
name = "Socks"
|
||||
sort_order = 3
|
||||
category_item_type = /datum/category_item/underwear/socks
|
||||
|
||||
/datum/category_group/underwear/undershirt
|
||||
name = "Undershirt"
|
||||
sort_order = 4 // Undershirts currently have the highest sort order because they may cover both underwear and socks.
|
||||
category_item_type = /datum/category_item/underwear/undershirt
|
||||
|
||||
/*******************
|
||||
* Category entries *
|
||||
*******************/
|
||||
/datum/category_item/underwear
|
||||
var/always_last = FALSE // Should this entry be sorte last?
|
||||
var/is_default = FALSE // Should this entry be considered the default for its type?
|
||||
var/icon = 'icons/mob/human.dmi' // Which icon to get the underwear from
|
||||
var/icon_state // And the particular item state
|
||||
var/list/tweaks = list() // Underwear customizations.
|
||||
var/has_color = FALSE
|
||||
|
||||
/datum/category_item/underwear/New()
|
||||
if(has_color)
|
||||
tweaks += gear_tweak_free_color_choice
|
||||
|
||||
/datum/category_item/underwear/dd_SortValue()
|
||||
if(always_last)
|
||||
return "~"+name
|
||||
return name
|
||||
|
||||
/datum/category_item/underwear/proc/is_default(var/gender)
|
||||
return is_default
|
||||
|
||||
/datum/category_item/underwear/proc/generate_image(var/list/metadata)
|
||||
if(!icon_state)
|
||||
return
|
||||
|
||||
var/image/I = image(icon = 'icons/mob/human.dmi', icon_state = icon_state)
|
||||
for(var/datum/gear_tweak/gt in tweaks)
|
||||
gt.tweak_item(I, metadata && metadata["[gt]"] ? metadata["[gt]"] : gt.get_default())
|
||||
return I
|
||||
@@ -70,7 +70,7 @@ var/datum/antagonist/deathsquad/deathsquad
|
||||
var/syndicate_commando_name = pick(last_names)
|
||||
|
||||
var/datum/preferences/A = new() //Randomize appearance for the commando.
|
||||
A.randomize_appearance_for(player.current)
|
||||
A.randomize_appearance_and_body_for(player.current)
|
||||
|
||||
player.name = "[syndicate_commando_rank] [syndicate_commando_name]"
|
||||
player.current.name = player.name
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
Shocking someone costs ten chemicals per use."
|
||||
enhancedtext = "Shocking biologicals without grabbing only requires five chemicals, and has more disabling power."
|
||||
genomecost = 2
|
||||
verbpath = /mob/proc/changeling_bioelectrogenesis
|
||||
verbpath = /mob/living/carbon/human/proc/changeling_bioelectrogenesis
|
||||
|
||||
//Recharge whatever's in our hand, or shock people.
|
||||
/mob/proc/changeling_bioelectrogenesis()
|
||||
/mob/living/carbon/human/proc/changeling_bioelectrogenesis()
|
||||
set category = "Changeling"
|
||||
set name = "Bioelectrogenesis (20 + 10/shock)"
|
||||
set desc = "Recharges anything in your hand, or shocks people."
|
||||
@@ -33,19 +33,28 @@
|
||||
return 0
|
||||
|
||||
else
|
||||
// Handle glove conductivity.
|
||||
var/obj/item/clothing/gloves/gloves = src.gloves
|
||||
var/siemens = 1
|
||||
if(gloves)
|
||||
siemens = gloves.siemens_coefficient
|
||||
|
||||
//If we're grabbing someone, electrocute them.
|
||||
if(istype(held_item,/obj/item/weapon/grab))
|
||||
var/obj/item/weapon/grab/G = held_item
|
||||
if(G.affecting)
|
||||
G.affecting.electrocute_act(10,src,1.0,BP_TORSO)
|
||||
var/agony = 80 //Does more than if hit with an electric hand, since grabbing is slower.
|
||||
G.affecting.electrocute_act(10 * siemens,src,1.0,BP_TORSO)
|
||||
var/agony = 80 * siemens //Does more than if hit with an electric hand, since grabbing is slower.
|
||||
G.affecting.stun_effect_act(0, agony, BP_TORSO, src)
|
||||
|
||||
msg_admin_attack("[key_name(src)] shocked [key_name(G.affecting)] with the [src].")
|
||||
|
||||
visible_message("<span class='warning'>Arcs of electricity strike [G.affecting]!</span>",
|
||||
"<span class='warning'>Our hand channels raw electricity into [G.affecting].</span>",
|
||||
"<span class='italics'>You hear sparks!</span>")
|
||||
if(siemens)
|
||||
visible_message("<span class='warning'>Arcs of electricity strike [G.affecting]!</span>",
|
||||
"<span class='warning'>Our hand channels raw electricity into [G.affecting].</span>",
|
||||
"<span class='italics'>You hear sparks!</span>")
|
||||
else
|
||||
src << "<span class='warning'>Our gloves block us from shocking \the [G.affecting].</span>"
|
||||
src.mind.changeling.chem_charges -= 10
|
||||
return 1
|
||||
|
||||
@@ -69,17 +78,19 @@
|
||||
"<span class='warning'>Our hand channels raw electricity into \the [held_item].</span>",
|
||||
"<span class='italics'>You hear sparks!</span>")
|
||||
var/i = 10
|
||||
while(i)
|
||||
cell.charge += 100 //This should be a nice compromise between recharging guns and other batteries.
|
||||
if(cell.charge > cell.maxcharge)
|
||||
cell.charge = cell.maxcharge
|
||||
break
|
||||
var/T = get_turf(src)
|
||||
new /obj/effect/effect/sparks(T)
|
||||
held_item.update_icon()
|
||||
i--
|
||||
sleep(1 SECOND)
|
||||
success = 1
|
||||
if(siemens)
|
||||
while(i)
|
||||
cell.charge += 100 * siemens //This should be a nice compromise between recharging guns and other batteries.
|
||||
if(cell.charge > cell.maxcharge)
|
||||
cell.charge = cell.maxcharge
|
||||
break
|
||||
if(siemens)
|
||||
var/T = get_turf(src)
|
||||
new /obj/effect/effect/sparks(T)
|
||||
held_item.update_icon()
|
||||
i--
|
||||
sleep(1 SECOND)
|
||||
success = 1
|
||||
if(success == 0) //If we couldn't do anything with the ability, don't deduct the chemicals.
|
||||
src << "<span class='warning'>We are unable to affect \the [held_item].</span>"
|
||||
else
|
||||
@@ -113,11 +124,18 @@
|
||||
if(src)
|
||||
qdel(src)
|
||||
|
||||
/obj/item/weapon/electric_hand/afterattack(var/atom/target, var/mob/living/user, proximity)
|
||||
/obj/item/weapon/electric_hand/afterattack(var/atom/target, var/mob/living/carbon/human/user, proximity)
|
||||
if(!target)
|
||||
return
|
||||
if(!proximity)
|
||||
return
|
||||
|
||||
// Handle glove conductivity.
|
||||
var/obj/item/clothing/gloves/gloves = user.gloves
|
||||
var/siemens = 1
|
||||
if(gloves)
|
||||
siemens = gloves.siemens_coefficient
|
||||
|
||||
//Excuse the copypasta.
|
||||
if(istype(target,/mob/living/carbon))
|
||||
var/mob/living/carbon/C = target
|
||||
@@ -126,14 +144,17 @@
|
||||
src << "<span class='warning'>We require more chemicals to electrocute [C]!</span>"
|
||||
return 0
|
||||
|
||||
C.electrocute_act(electrocute_amount,src,1.0,BP_TORSO)
|
||||
C.stun_effect_act(0, agony_amount, BP_TORSO, src)
|
||||
C.electrocute_act(electrocute_amount * siemens,src,1.0,BP_TORSO)
|
||||
C.stun_effect_act(0, agony_amount * siemens, BP_TORSO, src)
|
||||
|
||||
msg_admin_attack("[key_name(user)] shocked [key_name(C)] with the [src].")
|
||||
|
||||
visible_message("<span class='warning'>Arcs of electricity strike [C]!</span>",
|
||||
"<span class='warning'>Our hand channels raw electricity into [C]</span>",
|
||||
"<span class='italics'>You hear sparks!</span>")
|
||||
if(siemens)
|
||||
visible_message("<span class='warning'>Arcs of electricity strike [C]!</span>",
|
||||
"<span class='warning'>Our hand channels raw electricity into [C]</span>",
|
||||
"<span class='italics'>You hear sparks!</span>")
|
||||
else
|
||||
src << "<span class='warning'>Our gloves block us from shocking \the [C].</span>"
|
||||
//qdel(src) //Since we're no longer a one hit stun, we need to stick around.
|
||||
user.mind.changeling.chem_charges -= shock_cost
|
||||
return 1
|
||||
@@ -145,11 +166,12 @@
|
||||
src << "<span class='warning'>We require more chemicals to electrocute [S]!</span>"
|
||||
return 0
|
||||
|
||||
S.electrocute_act(60,src,1.0) //If only they had surge protectors.
|
||||
visible_message("<span class='warning'>Arcs of electricity strike [S]!</span>",
|
||||
"<span class='warning'>Our hand channels raw electricity into [S]</span>",
|
||||
"<span class='italics'>You hear sparks!</span>")
|
||||
S << "<span class='danger'>Warning: Electrical surge detected!</span>"
|
||||
S.electrocute_act(60 * siemens,src,1.0) //If only they had surge protectors.
|
||||
if(siemens)
|
||||
visible_message("<span class='warning'>Arcs of electricity strike [S]!</span>",
|
||||
"<span class='warning'>Our hand channels raw electricity into [S]</span>",
|
||||
"<span class='italics'>You hear sparks!</span>")
|
||||
S << "<span class='danger'>Warning: Electrical surge detected!</span>"
|
||||
//qdel(src)
|
||||
user.mind.changeling.chem_charges -= 10
|
||||
return 1
|
||||
@@ -164,20 +186,22 @@
|
||||
"<span class='warning'>Our hand channels raw electricity into \the [target].</span>",
|
||||
"<span class='italics'>You hear sparks!</span>")
|
||||
var/i = 10
|
||||
while(i)
|
||||
cell.charge += 100 //This should be a nice compromise between recharging guns and other batteries.
|
||||
if(cell.charge > cell.maxcharge)
|
||||
cell.charge = cell.maxcharge
|
||||
break //No point making sparks if the cell's full.
|
||||
// if(!Adjacent(T))
|
||||
// break
|
||||
var/Turf = get_turf(src)
|
||||
new /obj/effect/effect/sparks(Turf)
|
||||
T.update_icon()
|
||||
i--
|
||||
sleep(1 SECOND)
|
||||
success = 1
|
||||
break
|
||||
if(siemens)
|
||||
while(i)
|
||||
cell.charge += 100 * siemens //This should be a nice compromise between recharging guns and other batteries.
|
||||
if(cell.charge > cell.maxcharge)
|
||||
cell.charge = cell.maxcharge
|
||||
break //No point making sparks if the cell's full.
|
||||
// if(!Adjacent(T))
|
||||
// break
|
||||
if(siemens)
|
||||
var/Turf = get_turf(src)
|
||||
new /obj/effect/effect/sparks(Turf)
|
||||
T.update_icon()
|
||||
i--
|
||||
sleep(1 SECOND)
|
||||
success = 1
|
||||
break
|
||||
if(success == 0)
|
||||
src << "<span class='warning'>We are unable to affect \the [target].</span>"
|
||||
else
|
||||
|
||||
@@ -18,14 +18,13 @@
|
||||
var/mob/living/carbon/C = src
|
||||
|
||||
if(changeling.max_geneticpoints < 0) //Absorbed by another ling
|
||||
src << "<span class='danger'>You have no genomes, not even your own, and cannot regenerate.</span>"
|
||||
src << "<span class='danger'>We have no genomes, not even our own, and cannot regenerate.</span>"
|
||||
return 0
|
||||
|
||||
if(!C.stat && alert("Are we sure we wish to fake our death?",,"Yes","No") == "No")//Confirmation for living changelings if they want to fake their death
|
||||
if(!C.stat && alert("Are we sure we wish to regenerate? We will appear to be dead while doing so.","Revival","Yes","No") == "No")
|
||||
return
|
||||
C << "<span class='notice'>We will attempt to regenerate our form.</span>"
|
||||
|
||||
C.status_flags |= FAKEDEATH //play dead
|
||||
C.update_canmove()
|
||||
C.remove_changeling_powers()
|
||||
|
||||
@@ -33,13 +32,12 @@
|
||||
C.suiciding = 0
|
||||
|
||||
if(C.stat != DEAD)
|
||||
C.emote("deathgasp")
|
||||
C.tod = worldtime2text()
|
||||
C.adjustOxyLoss(C.maxHealth * 2)
|
||||
|
||||
spawn(rand(800,2000))
|
||||
//The ling will now be able to choose when to revive
|
||||
src.verbs += /mob/proc/changeling_revive
|
||||
src << "<span class='notice'>We are ready to rise. Use the Revive verb when you are ready.</span>"
|
||||
src << "<span class='notice'><font size='5'>We are ready to rise. Use the <b>Revive</b> verb when you are ready.</font></span>"
|
||||
|
||||
feedback_add_details("changeling_powers","FD")
|
||||
return 1
|
||||
@@ -25,8 +25,6 @@
|
||||
C.SetStunned(0)
|
||||
C.SetWeakened(0)
|
||||
C.radiation = 0
|
||||
C.halloss = 0
|
||||
C.shock_stage = 0 //Pain
|
||||
C.heal_overall_damage(C.getBruteLoss(), C.getFireLoss())
|
||||
C.reagents.clear_reagents()
|
||||
C.restore_all_organs(ignore_prosthetic_prefs=1) //Covers things like fractures and other things not covered by the above.
|
||||
@@ -36,8 +34,13 @@
|
||||
H.mutations.Remove(HUSK)
|
||||
H.status_flags -= DISFIGURED
|
||||
H.update_body(1)
|
||||
for(var/limb in H.organs_by_name)
|
||||
var/obj/item/organ/external/current_limb = H.organs_by_name[limb]
|
||||
current_limb.undislocate()
|
||||
|
||||
C.halloss = 0
|
||||
C.shock_stage = 0 //Pain
|
||||
C << "<span class='notice'>We have regenerated.</span>"
|
||||
C.status_flags &= ~FAKEDEATH
|
||||
C.update_canmove()
|
||||
C.mind.changeling.purchased_powers -= C
|
||||
feedback_add_details("changeling_powers","CR")
|
||||
|
||||
@@ -6,23 +6,35 @@
|
||||
w_class = 4
|
||||
force = 30
|
||||
throwforce = 10
|
||||
hitsound = 'sound/weapons/bladeslice.ogg'
|
||||
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
|
||||
|
||||
/obj/item/weapon/melee/cultblade/cultify()
|
||||
return
|
||||
|
||||
/obj/item/weapon/melee/cultblade/attack(mob/living/target as mob, mob/living/carbon/human/user as mob)
|
||||
/obj/item/weapon/melee/cultblade/attack(mob/living/M, mob/living/user, var/target_zone)
|
||||
if(iscultist(user))
|
||||
playsound(loc, 'sound/weapons/bladeslice.ogg', 50, 1, -1)
|
||||
return ..()
|
||||
|
||||
var/zone = (user.hand ? "l_arm":"r_arm")
|
||||
if(ishuman(user))
|
||||
var/mob/living/carbon/human/H = user
|
||||
var/obj/item/organ/external/affecting = H.get_organ(zone)
|
||||
user << "<span class='danger'>An unexplicable force rips through your [affecting.name], tearing the sword from your grasp!</span>"
|
||||
else
|
||||
user.Paralyse(5)
|
||||
user << "<span class='warning'>An unexplicable force powerfully repels the sword from [target]!</span>"
|
||||
var/organ = ((user.hand ? "l_":"r_") + "arm")
|
||||
var/obj/item/organ/external/affecting = user.get_organ(organ)
|
||||
if(affecting.take_damage(rand(force/2, force))) //random amount of damage between half of the blade's force and the full force of the blade.
|
||||
user.UpdateDamageIcon()
|
||||
return
|
||||
user << "<span class='danger'>An unexplicable force rips through you, tearing the sword from your grasp!</span>"
|
||||
|
||||
//random amount of damage between half of the blade's force and the full force of the blade.
|
||||
user.apply_damage(rand(force/2, force), BRUTE, zone, 0, sharp=1, edge=1)
|
||||
user.Weaken(5)
|
||||
|
||||
user.drop_from_inventory(src, src.loc)
|
||||
throw_at(get_edge_target_turf(src, pick(alldirs)), rand(1,3), throw_speed)
|
||||
|
||||
var/spooky = pick('sound/hallucinations/growl1.ogg', 'sound/hallucinations/growl2.ogg', 'sound/hallucinations/growl3.ogg', 'sound/hallucinations/wail.ogg')
|
||||
playsound(loc, spooky, 50, 1)
|
||||
|
||||
return 1
|
||||
|
||||
/obj/item/weapon/melee/cultblade/pickup(mob/living/user as mob)
|
||||
if(!iscultist(user))
|
||||
|
||||
@@ -470,8 +470,7 @@ var/list/sacrificed = list()
|
||||
D.r_eyes = 200
|
||||
D.g_eyes = 200
|
||||
D.update_eyes()
|
||||
D.underwear_top = 0
|
||||
D.underwear_bottom = 0
|
||||
D.all_underwear.Cut()
|
||||
D.key = ghost.key
|
||||
cult.add_antagonist(D.mind)
|
||||
|
||||
|
||||
@@ -13,21 +13,16 @@
|
||||
minimal_access = list() //See /datum/job/assistant/get_access()
|
||||
alt_titles = list("Technical Assistant","Medical Intern","Research Assistant","Visitor", "Resident")
|
||||
|
||||
/datum/job/assistant/equip(var/mob/living/carbon/human/H)
|
||||
/datum/job/assistant/equip(var/mob/living/carbon/human/H, var/alt_title)
|
||||
if(!H) return 0
|
||||
switch(H.backbag)
|
||||
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
|
||||
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel/norm(H), slot_back)
|
||||
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
|
||||
if (H.mind.role_alt_title)
|
||||
switch(H.mind.role_alt_title)
|
||||
if("Visitor") //I doubt someone visiting the station would want to wear an ugly grey uniform
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/assistantformal(H), slot_w_uniform)
|
||||
|
||||
if("Resident")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/color/white(H), slot_w_uniform)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/color/grey(H), slot_w_uniform)
|
||||
if(has_alt_title(H, alt_title,"Visitor")) //I doubt someone visiting the station would want to wear an ugly grey uniform
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/assistantformal(H), slot_w_uniform)
|
||||
else if(has_alt_title(H, alt_title,"Resident"))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/color/white(H), slot_w_uniform)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/color/grey(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
alt_titles = list("Counselor")
|
||||
|
||||
|
||||
equip(var/mob/living/carbon/human/H)
|
||||
equip(var/mob/living/carbon/human/H, var/alt_title, var/ask_questions = TRUE)
|
||||
if(!H) return 0
|
||||
|
||||
var/obj/item/weapon/storage/bible/B = new /obj/item/weapon/storage/bible(H) //BS12 EDIT
|
||||
@@ -22,6 +22,10 @@
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/chaplain(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/device/pda/chaplain(H), slot_belt)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes)
|
||||
if(!ask_questions)
|
||||
return 1
|
||||
|
||||
|
||||
spawn(0)
|
||||
var/religion_name = "Christianity"
|
||||
var/new_religion = sanitize(input(H, "You are the crew services officer. Would you like to change your religion? Default is Christianity, in SPACE.", "Name change", religion_name), MAX_NAME_LEN)
|
||||
@@ -150,3 +154,6 @@
|
||||
feedback_set_details("religion_deity","[new_deity]")
|
||||
feedback_set_details("religion_book","[new_book_style]")
|
||||
return 1
|
||||
|
||||
/datum/job/chaplain/equip_preview(var/mob/living/carbon/human/H, var/alt_title)
|
||||
return equip(H, alt_title, FALSE)
|
||||
|
||||
@@ -71,8 +71,8 @@
|
||||
H << "<span class='notice'><b>Your account number is: [M.account_number], your account pin is: [M.remote_access_pin]</b></span>"
|
||||
|
||||
// overrideable separately so AIs/borgs can have cardborg hats without unneccessary new()/del()
|
||||
/datum/job/proc/equip_preview(mob/living/carbon/human/H)
|
||||
return equip(H)
|
||||
/datum/job/proc/equip_preview(mob/living/carbon/human/H, var/alt_title)
|
||||
. = equip(H, alt_title)
|
||||
|
||||
/datum/job/proc/get_access()
|
||||
if(!config || config.jobs_have_minimal_access)
|
||||
@@ -104,3 +104,6 @@
|
||||
|
||||
/datum/job/proc/is_position_available()
|
||||
return (current_positions < total_positions) || (total_positions == -1)
|
||||
|
||||
/datum/job/proc/has_alt_title(var/mob/H, var/supplied_title, var/desired_title)
|
||||
return (supplied_title == desired_title) || (H.mind && H.mind.role_alt_title == desired_title)
|
||||
@@ -23,7 +23,7 @@
|
||||
minimal_player_age = 10
|
||||
ideal_character_age = 50
|
||||
|
||||
equip(var/mob/living/carbon/human/H)
|
||||
equip(var/mob/living/carbon/human/H, var/alt_title)
|
||||
if(!H) return 0
|
||||
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/heads/cmo(H), slot_l_ear)
|
||||
switch(H.backbag)
|
||||
@@ -53,7 +53,7 @@
|
||||
minimal_access = list(access_medical, access_medical_equip, access_morgue, access_surgery, access_virology, access_eva)
|
||||
alt_titles = list("Surgeon","Emergency Physician","Nurse","Virologist")
|
||||
|
||||
equip(var/mob/living/carbon/human/H)
|
||||
equip(var/mob/living/carbon/human/H, var/alt_title)
|
||||
if(!H) return 0
|
||||
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_med(H), slot_l_ear)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/white(H), slot_shoes)
|
||||
@@ -62,35 +62,33 @@
|
||||
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/medic(H), slot_back)
|
||||
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel/med(H), slot_back)
|
||||
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
|
||||
if (H.mind.role_alt_title)
|
||||
switch(H.mind.role_alt_title)
|
||||
if("Emergency Physician")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/fr_jacket(H), slot_wear_suit)
|
||||
if("Surgeon")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical/blue(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat(H), slot_wear_suit)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/head/surgery/blue(H), slot_head)
|
||||
if("Virologist")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/virologist(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat/virologist(H), slot_wear_suit)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/mask/surgical(H), slot_wear_mask)
|
||||
switch(H.backbag)
|
||||
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/virology(H), slot_back)
|
||||
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel/vir(H), slot_back)
|
||||
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
|
||||
if("Medical Doctor")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat(H), slot_wear_suit)
|
||||
if("Nurse")
|
||||
if(H.gender == FEMALE)
|
||||
if(prob(50))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/nursesuit(H), slot_w_uniform)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/nurse(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/head/nursehat(H), slot_head)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical/purple(H), slot_w_uniform)
|
||||
if(has_alt_title(H, alt_title,"Emergency Physician"))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/fr_jacket(H), slot_wear_suit)
|
||||
else if(has_alt_title(H, alt_title,"Surgeon"))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical/blue(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat(H), slot_wear_suit)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/head/surgery/blue(H), slot_head)
|
||||
else if(has_alt_title(H, alt_title,"Virologist"))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/virologist(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat/virologist(H), slot_wear_suit)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/mask/surgical(H), slot_wear_mask)
|
||||
switch(H.backbag)
|
||||
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/virology(H), slot_back)
|
||||
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel/vir(H), slot_back)
|
||||
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
|
||||
else if(has_alt_title(H, alt_title,"Medical Doctor"))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat(H), slot_wear_suit)
|
||||
else if(has_alt_title(H, alt_title,"Nurse"))
|
||||
if(H.gender == FEMALE)
|
||||
if(prob(50))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/nursesuit(H), slot_w_uniform)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/nurse(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/head/nursehat(H), slot_head)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical/purple(H), slot_w_uniform)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat(H), slot_wear_suit)
|
||||
@@ -176,19 +174,17 @@
|
||||
minimal_access = list(access_medical, access_medical_equip, access_psychiatrist)
|
||||
alt_titles = list("Psychologist")
|
||||
|
||||
equip(var/mob/living/carbon/human/H)
|
||||
equip(var/mob/living/carbon/human/H, var/alt_title)
|
||||
if(!H) return 0
|
||||
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_med(H), slot_l_ear)
|
||||
switch(H.backbag)
|
||||
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
|
||||
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel/norm(H), slot_back)
|
||||
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
|
||||
if (H.mind.role_alt_title)
|
||||
switch(H.mind.role_alt_title)
|
||||
if("Psychiatrist")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/psych(H), slot_w_uniform)
|
||||
if("Psychologist")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/psych/turtleneck(H), slot_w_uniform)
|
||||
if(has_alt_title(H, alt_title,"Psychiatrist"))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/psych(H), slot_w_uniform)
|
||||
else if(has_alt_title(H, alt_title,"Psychologist"))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/psych/turtleneck(H), slot_w_uniform)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/laceup(H), slot_shoes)
|
||||
@@ -211,7 +207,7 @@
|
||||
minimal_access = list(access_medical, access_medical_equip, access_morgue, access_eva, access_maint_tunnels, access_external_airlocks)
|
||||
alt_titles = list("Emergency Medical Technician")
|
||||
|
||||
equip(var/mob/living/carbon/human/H)
|
||||
equip(var/mob/living/carbon/human/H, var/alt_title)
|
||||
if(!H) return 0
|
||||
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_med(H), slot_l_ear)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/jackboots(H), slot_shoes)
|
||||
@@ -220,14 +216,12 @@
|
||||
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/medic(H), slot_back)
|
||||
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel/med(H), slot_back)
|
||||
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
|
||||
if (H.mind.role_alt_title)
|
||||
switch(H.mind.role_alt_title)
|
||||
if("Emergency Medical Technician")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical/paramedic(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/fr_jacket(H), slot_wear_suit)
|
||||
if("Paramedic")
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical/black(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/fr_jacket(H), slot_wear_suit)
|
||||
if(has_alt_title(H, alt_title,"Emergency Medical Technician"))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical/paramedic(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/fr_jacket(H), slot_wear_suit)
|
||||
else if(has_alt_title(H, alt_title,"Paramedic"))
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical/black(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/fr_jacket(H), slot_wear_suit)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical(H), slot_w_uniform)
|
||||
H.equip_to_slot_or_del(new /obj/item/weapon/storage/belt/medical/emt(H), slot_belt)
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
minimal_access = list(access_security, access_sec_doors, access_forensics_lockers, access_morgue, access_maint_tunnels, access_eva, access_external_airlocks)
|
||||
economic_modifier = 5
|
||||
minimal_player_age = 3
|
||||
equip(var/mob/living/carbon/human/H)
|
||||
equip(var/mob/living/carbon/human/H, var/alt_title)
|
||||
if(!H) return 0
|
||||
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_sec(H), slot_l_ear)
|
||||
switch(H.backbag)
|
||||
@@ -114,7 +114,7 @@
|
||||
H.equip_to_slot_or_del(new /obj/item/weapon/storage/box/evidence(H), slot_l_hand)
|
||||
else
|
||||
H.equip_to_slot_or_del(new /obj/item/weapon/storage/box/evidence(H), slot_in_backpack)
|
||||
if(H.mind.role_alt_title && H.mind.role_alt_title == "Forensic Technician")
|
||||
if(has_alt_title(H, 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
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
/datum/job/ai/equip_preview(mob/living/carbon/human/H)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/straight_jacket(H), slot_wear_suit)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/head/cardborg(H), slot_head)
|
||||
return 1
|
||||
|
||||
/datum/job/cyborg
|
||||
title = "Cyborg"
|
||||
@@ -60,3 +61,4 @@
|
||||
/datum/job/cyborg/equip_preview(mob/living/carbon/human/H)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/suit/cardborg(H), slot_wear_suit)
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/head/cardborg(H), slot_head)
|
||||
return 1
|
||||
|
||||
@@ -360,7 +360,7 @@
|
||||
return
|
||||
|
||||
if(subject.isSynthetic())
|
||||
scantemp = "Error: Subject is not organic."
|
||||
scantemp = "Error: Majority of subject is non-organic."
|
||||
return
|
||||
if (subject.suiciding == 1)
|
||||
scantemp = "Error: Subject's brain is not responding to scanning stimuli."
|
||||
|
||||
@@ -67,9 +67,12 @@
|
||||
return 0
|
||||
|
||||
/obj/machinery/smartfridge/secure/extract/New()
|
||||
..()
|
||||
for(var/i=1 to 5)
|
||||
var/obj/item/xenoproduct/slime/core/C = new(src)
|
||||
C.traits = new()
|
||||
C.nameVar = "grey"
|
||||
item_quants[C.name]++
|
||||
|
||||
/obj/machinery/smartfridge/secure/medbay
|
||||
name = "\improper Refrigerated Medicine Storage"
|
||||
|
||||
@@ -7,7 +7,7 @@ obj/machinery/recharger
|
||||
anchored = 1
|
||||
use_power = 1
|
||||
idle_power_usage = 4
|
||||
active_power_usage = 15000 //15 kW
|
||||
active_power_usage = 40000 //40 kW
|
||||
var/obj/item/charging = null
|
||||
var/list/allowed_devices = list(/obj/item/weapon/gun/energy, /obj/item/weapon/melee/baton, /obj/item/device/laptop, /obj/item/weapon/cell)
|
||||
var/icon_state_charged = "recharger2"
|
||||
@@ -171,4 +171,4 @@ obj/machinery/recharger/wallcharger
|
||||
icon_state_idle = "wrecharger0"
|
||||
portable = 0
|
||||
circuit = /obj/item/weapon/circuitboard/recharger/wrecharger
|
||||
frame_type = "wrecharger"
|
||||
frame_type = "wrecharger"
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
name = "space heater"
|
||||
desc = "Made by Space Amish using traditional space techniques, this heater is guaranteed not to set the station on fire."
|
||||
var/obj/item/weapon/cell/cell
|
||||
var/cell_type = /obj/item/weapon/cell/apc
|
||||
var/cell_type = /obj/item/weapon/cell/high
|
||||
var/on = 0
|
||||
var/set_temperature = T0C + 50 //K
|
||||
var/set_temperature = T0C + 20 //K
|
||||
var/heating_power = 40000
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
icon = 'icons/obj/robotics.dmi'
|
||||
icon_state = "fab-idle"
|
||||
name = "Exosuit Fabricator"
|
||||
desc = "A machine used for construction of robotcs and mechas."
|
||||
desc = "A machine used for construction of mechas."
|
||||
density = 1
|
||||
anchored = 1
|
||||
use_power = 1
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
var/list/categories = list()
|
||||
var/category = null
|
||||
var/manufacturer = null
|
||||
var/sync_message = ""
|
||||
|
||||
/obj/machinery/mecha_part_fabricator/New()
|
||||
@@ -41,7 +40,6 @@
|
||||
return
|
||||
|
||||
/obj/machinery/mecha_part_fabricator/initialize()
|
||||
manufacturer = basic_robolimb.company
|
||||
update_categories()
|
||||
|
||||
/obj/machinery/mecha_part_fabricator/process()
|
||||
@@ -99,13 +97,6 @@
|
||||
data["buildable"] = get_build_options()
|
||||
data["category"] = category
|
||||
data["categories"] = categories
|
||||
if(all_robolimbs)
|
||||
var/list/T = list()
|
||||
for(var/A in all_robolimbs)
|
||||
var/datum/robolimb/R = all_robolimbs[A]
|
||||
T += list(list("id" = A, "company" = R.company))
|
||||
data["manufacturers"] = T
|
||||
data["manufacturer"] = manufacturer
|
||||
data["materials"] = get_materials()
|
||||
data["maxres"] = res_max_amount
|
||||
data["sync"] = sync_message
|
||||
@@ -133,10 +124,6 @@
|
||||
if(href_list["category"] in categories)
|
||||
category = href_list["category"]
|
||||
|
||||
if(href_list["manufacturer"])
|
||||
if(href_list["manufacturer"] in all_robolimbs)
|
||||
manufacturer = href_list["manufacturer"]
|
||||
|
||||
if(href_list["eject"])
|
||||
eject_materials(href_list["eject"], text2num(href_list["amount"]))
|
||||
|
||||
@@ -158,43 +145,32 @@
|
||||
if(default_part_replacement(user, I))
|
||||
return
|
||||
|
||||
var/material
|
||||
switch(I.type)
|
||||
if(/obj/item/stack/material/gold)
|
||||
material = "gold"
|
||||
if(/obj/item/stack/material/silver)
|
||||
material = "silver"
|
||||
if(/obj/item/stack/material/diamond)
|
||||
material = "diamond"
|
||||
if(/obj/item/stack/material/phoron)
|
||||
material = "phoron"
|
||||
if(/obj/item/stack/material/steel)
|
||||
material = DEFAULT_WALL_MATERIAL
|
||||
if(/obj/item/stack/material/glass)
|
||||
material = "glass"
|
||||
if(/obj/item/stack/material/uranium)
|
||||
material = "uranium"
|
||||
if(istype(I,/obj/item/stack/material))
|
||||
var/obj/item/stack/material/S = I
|
||||
if(!(S.material.name in materials))
|
||||
user << "<span class='warning'>The [src] doesn't accept [S.material]!</span>"
|
||||
return
|
||||
|
||||
var/sname = "[S.name]"
|
||||
var/amnt = S.perunit
|
||||
if(materials[S.material.name] + amnt <= res_max_amount)
|
||||
if(S && S.amount >= 1)
|
||||
var/count = 0
|
||||
overlays += "fab-load-metal"
|
||||
spawn(10)
|
||||
overlays -= "fab-load-metal"
|
||||
while(materials[S.material.name] + amnt <= res_max_amount && S.amount >= 1)
|
||||
materials[S.material.name] += amnt
|
||||
S.use(1)
|
||||
count++
|
||||
user << "You insert [count] [sname] into the fabricator."
|
||||
update_busy()
|
||||
else
|
||||
return ..()
|
||||
user << "The fabricator cannot hold more [sname]."
|
||||
|
||||
var/obj/item/stack/material/stack = I
|
||||
var/sname = "[stack.name]"
|
||||
var/amnt = stack.perunit
|
||||
return
|
||||
|
||||
if(materials[material] + amnt <= res_max_amount)
|
||||
if(stack && stack.amount >= 1)
|
||||
var/count = 0
|
||||
overlays += "fab-load-metal"
|
||||
spawn(10)
|
||||
overlays -= "fab-load-metal"
|
||||
while(materials[material] + amnt <= res_max_amount && stack.amount >= 1)
|
||||
materials[material] += amnt
|
||||
stack.use(1)
|
||||
count++
|
||||
user << "You insert [count] [sname] into the fabricator."
|
||||
update_busy()
|
||||
else
|
||||
user << "The fabricator cannot hold more [sname]."
|
||||
..()
|
||||
|
||||
/obj/machinery/mecha_part_fabricator/emag_act(var/remaining_charges, var/mob/user)
|
||||
switch(emagged)
|
||||
@@ -254,7 +230,7 @@
|
||||
for(var/M in D.materials)
|
||||
materials[M] = max(0, materials[M] - D.materials[M] * mat_efficiency)
|
||||
if(D.build_path)
|
||||
var/obj/new_item = D.Fabricate(loc, src)
|
||||
var/obj/new_item = D.Fabricate(get_step(get_turf(src), src.dir), src)
|
||||
visible_message("\The [src] pings, indicating that \the [D] is complete.", "You hear a ping.")
|
||||
if(mat_efficiency != 1)
|
||||
if(new_item.matter && new_item.matter.len > 0)
|
||||
@@ -301,36 +277,20 @@
|
||||
|
||||
/obj/machinery/mecha_part_fabricator/proc/eject_materials(var/material, var/amount) // 0 amount = 0 means ejecting a full stack; -1 means eject everything
|
||||
var/recursive = amount == -1 ? 1 : 0
|
||||
material = lowertext(material)
|
||||
var/mattype
|
||||
switch(material)
|
||||
if(DEFAULT_WALL_MATERIAL)
|
||||
mattype = /obj/item/stack/material/steel
|
||||
if("glass")
|
||||
mattype = /obj/item/stack/material/glass
|
||||
if("gold")
|
||||
mattype = /obj/item/stack/material/gold
|
||||
if("silver")
|
||||
mattype = /obj/item/stack/material/silver
|
||||
if("diamond")
|
||||
mattype = /obj/item/stack/material/diamond
|
||||
if("phoron")
|
||||
mattype = /obj/item/stack/material/phoron
|
||||
if("uranium")
|
||||
mattype = /obj/item/stack/material/uranium
|
||||
else
|
||||
return
|
||||
var/obj/item/stack/material/S = new mattype(loc)
|
||||
var/matstring = lowertext(material)
|
||||
var/material/M = get_material_by_name(matstring)
|
||||
|
||||
var/obj/item/stack/material/S = M.place_sheet(get_turf(src))
|
||||
if(amount <= 0)
|
||||
amount = S.max_amount
|
||||
var/ejected = min(round(materials[material] / S.perunit), amount)
|
||||
var/ejected = min(round(materials[matstring] / S.perunit), amount)
|
||||
S.amount = min(ejected, amount)
|
||||
if(S.amount <= 0)
|
||||
qdel(S)
|
||||
return
|
||||
materials[material] -= ejected * S.perunit
|
||||
if(recursive && materials[material] >= S.perunit)
|
||||
eject_materials(material, -1)
|
||||
materials[matstring] -= ejected * S.perunit
|
||||
if(recursive && materials[matstring] >= S.perunit)
|
||||
eject_materials(matstring, -1)
|
||||
update_busy()
|
||||
|
||||
/obj/machinery/mecha_part_fabricator/proc/sync()
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
/obj/machinery/pros_fabricator
|
||||
icon = 'icons/obj/robotics.dmi'
|
||||
icon_state = "pros-idle"
|
||||
name = "Prosthetics Fabricator"
|
||||
desc = "A machine used for construction of prosthetics."
|
||||
density = 1
|
||||
anchored = 1
|
||||
use_power = 1
|
||||
idle_power_usage = 20
|
||||
active_power_usage = 5000
|
||||
req_access = list(access_robotics)
|
||||
circuit = /obj/item/weapon/circuitboard/prosthetics
|
||||
|
||||
var/speed = 1
|
||||
var/mat_efficiency = 1
|
||||
var/list/materials = list(DEFAULT_WALL_MATERIAL = 0, "glass" = 0, "gold" = 0, "silver" = 0, "diamond" = 0, "phoron" = 0, "uranium" = 0, "plasteel" = 0)
|
||||
var/res_max_amount = 200000
|
||||
|
||||
var/datum/research/files
|
||||
var/list/datum/design/queue = list()
|
||||
var/progress = 0
|
||||
var/busy = 0
|
||||
|
||||
var/list/categories = list()
|
||||
var/category = null
|
||||
var/manufacturer = null
|
||||
var/sync_message = ""
|
||||
|
||||
/obj/machinery/pros_fabricator/New()
|
||||
..()
|
||||
circuit = new circuit(src)
|
||||
component_parts = list()
|
||||
component_parts += new /obj/item/weapon/stock_parts/matter_bin(src)
|
||||
component_parts += new /obj/item/weapon/stock_parts/matter_bin(src)
|
||||
component_parts += new /obj/item/weapon/stock_parts/manipulator(src)
|
||||
component_parts += new /obj/item/weapon/stock_parts/micro_laser(src)
|
||||
component_parts += new /obj/item/weapon/stock_parts/console_screen(src)
|
||||
RefreshParts()
|
||||
|
||||
files = new /datum/research(src) //Setup the research data holder.
|
||||
return
|
||||
|
||||
/obj/machinery/pros_fabricator/initialize()
|
||||
manufacturer = basic_robolimb.company
|
||||
update_categories()
|
||||
|
||||
/obj/machinery/pros_fabricator/process()
|
||||
..()
|
||||
if(stat)
|
||||
return
|
||||
if(busy)
|
||||
use_power = 2
|
||||
progress += speed
|
||||
check_build()
|
||||
else
|
||||
use_power = 1
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/pros_fabricator/update_icon()
|
||||
overlays.Cut()
|
||||
if(panel_open)
|
||||
icon_state = "pros-o"
|
||||
else
|
||||
icon_state = "pros-idle"
|
||||
if(busy)
|
||||
overlays += "pros-active"
|
||||
|
||||
/obj/machinery/pros_fabricator/dismantle()
|
||||
for(var/f in materials)
|
||||
eject_materials(f, -1)
|
||||
..()
|
||||
|
||||
/obj/machinery/pros_fabricator/RefreshParts()
|
||||
res_max_amount = 0
|
||||
for(var/obj/item/weapon/stock_parts/matter_bin/M in component_parts)
|
||||
res_max_amount += M.rating * 100000 // 200k -> 600k
|
||||
var/T = 0
|
||||
for(var/obj/item/weapon/stock_parts/manipulator/M in component_parts)
|
||||
T += M.rating
|
||||
mat_efficiency = 1 - (T - 1) / 4 // 1 -> 0.5
|
||||
for(var/obj/item/weapon/stock_parts/micro_laser/M in component_parts) // Not resetting T is intended; speed is affected by both
|
||||
T += M.rating
|
||||
speed = T / 2 // 1 -> 3
|
||||
|
||||
/obj/machinery/pros_fabricator/attack_hand(var/mob/user)
|
||||
if(..())
|
||||
return
|
||||
if(!allowed(user))
|
||||
return
|
||||
ui_interact(user)
|
||||
|
||||
/obj/machinery/pros_fabricator/ui_interact(var/mob/user, var/ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
|
||||
var/data[0]
|
||||
|
||||
var/datum/design/current = queue.len ? queue[1] : null
|
||||
if(current)
|
||||
data["current"] = current.name
|
||||
data["queue"] = get_queue_names()
|
||||
data["buildable"] = get_build_options()
|
||||
data["category"] = category
|
||||
data["categories"] = categories
|
||||
if(all_robolimbs)
|
||||
var/list/T = list()
|
||||
for(var/A in all_robolimbs)
|
||||
var/datum/robolimb/R = all_robolimbs[A]
|
||||
if(R.unavailable_to_build) continue
|
||||
T += list(list("id" = A, "company" = R.company))
|
||||
data["manufacturers"] = T
|
||||
data["manufacturer"] = manufacturer
|
||||
data["materials"] = get_materials()
|
||||
data["maxres"] = res_max_amount
|
||||
data["sync"] = sync_message
|
||||
if(current)
|
||||
data["builtperc"] = round((progress / current.time) * 100)
|
||||
|
||||
ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "mechfab.tmpl", "Prosthetics Fab UI", 800, 600)
|
||||
ui.set_initial_data(data)
|
||||
ui.open()
|
||||
ui.set_auto_update(1)
|
||||
|
||||
/obj/machinery/pros_fabricator/Topic(href, href_list)
|
||||
if(..())
|
||||
return
|
||||
|
||||
if(href_list["build"])
|
||||
add_to_queue(text2num(href_list["build"]))
|
||||
|
||||
if(href_list["remove"])
|
||||
remove_from_queue(text2num(href_list["remove"]))
|
||||
|
||||
if(href_list["category"])
|
||||
if(href_list["category"] in categories)
|
||||
category = href_list["category"]
|
||||
|
||||
if(href_list["manufacturer"])
|
||||
if(href_list["manufacturer"] in all_robolimbs)
|
||||
manufacturer = href_list["manufacturer"]
|
||||
|
||||
if(href_list["eject"])
|
||||
eject_materials(href_list["eject"], text2num(href_list["amount"]))
|
||||
|
||||
if(href_list["sync"])
|
||||
sync()
|
||||
else
|
||||
sync_message = ""
|
||||
|
||||
return 1
|
||||
|
||||
/obj/machinery/pros_fabricator/attackby(var/obj/item/I, var/mob/user)
|
||||
if(busy)
|
||||
user << "<span class='notice'>\The [src] is busy. Please wait for completion of previous operation.</span>"
|
||||
return 1
|
||||
if(default_deconstruction_screwdriver(user, I))
|
||||
return
|
||||
if(default_deconstruction_crowbar(user, I))
|
||||
return
|
||||
if(default_part_replacement(user, I))
|
||||
return
|
||||
|
||||
if(istype(I,/obj/item/weapon/disk/limb))
|
||||
var/obj/item/weapon/disk/limb/D = I
|
||||
if(!D.company || !(D.company in all_robolimbs))
|
||||
user << "<span class='warning'>This disk seems to be corrupted!</span>"
|
||||
else
|
||||
user << "<span class='notice'>Installing blueprint files for [D.company]...</span>"
|
||||
if(do_after(user,50,src))
|
||||
var/datum/robolimb/R = all_robolimbs[D.company]
|
||||
R.unavailable_to_build = 0
|
||||
user << "<span class='notice'>Installed [D.company] blueprints!</span>"
|
||||
qdel(I)
|
||||
return
|
||||
|
||||
if(istype(I,/obj/item/stack/material))
|
||||
var/obj/item/stack/material/S = I
|
||||
if(!(S.material.name in materials))
|
||||
user << "<span class='warning'>The [src] doesn't accept [S.material]!</span>"
|
||||
return
|
||||
|
||||
var/sname = "[S.name]"
|
||||
var/amnt = S.perunit
|
||||
if(materials[S.material.name] + amnt <= res_max_amount)
|
||||
if(S && S.amount >= 1)
|
||||
var/count = 0
|
||||
overlays += "pros-load-metal"
|
||||
spawn(10)
|
||||
overlays -= "pros-load-metal"
|
||||
while(materials[S.material.name] + amnt <= res_max_amount && S.amount >= 1)
|
||||
materials[S.material.name] += amnt
|
||||
S.use(1)
|
||||
count++
|
||||
user << "You insert [count] [sname] into the fabricator."
|
||||
update_busy()
|
||||
else
|
||||
user << "The fabricator cannot hold more [sname]."
|
||||
|
||||
return
|
||||
|
||||
..()
|
||||
|
||||
/obj/machinery/pros_fabricator/emag_act(var/remaining_charges, var/mob/user)
|
||||
switch(emagged)
|
||||
if(0)
|
||||
emagged = 0.5
|
||||
visible_message("\icon[src] <b>[src]</b> beeps: \"DB error \[Code 0x00F1\]\"")
|
||||
sleep(10)
|
||||
visible_message("\icon[src] <b>[src]</b> beeps: \"Attempting auto-repair\"")
|
||||
sleep(15)
|
||||
visible_message("\icon[src] <b>[src]</b> beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"")
|
||||
sleep(30)
|
||||
visible_message("\icon[src] <b>[src]</b> beeps: \"User DB truncated. Please contact your [company_name] system operator for future assistance.\"")
|
||||
req_access = null
|
||||
emagged = 1
|
||||
return 1
|
||||
if(0.5)
|
||||
visible_message("\icon[src] <b>[src]</b> beeps: \"DB not responding \[Code 0x0003\]...\"")
|
||||
if(1)
|
||||
visible_message("\icon[src] <b>[src]</b> beeps: \"No records in User DB\"")
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/update_busy()
|
||||
if(queue.len)
|
||||
if(can_build(queue[1]))
|
||||
busy = 1
|
||||
else
|
||||
busy = 0
|
||||
else
|
||||
busy = 0
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/add_to_queue(var/index)
|
||||
var/datum/design/D = files.known_designs[index]
|
||||
queue += D
|
||||
update_busy()
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/remove_from_queue(var/index)
|
||||
if(index == 1)
|
||||
progress = 0
|
||||
queue.Cut(index, index + 1)
|
||||
update_busy()
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/can_build(var/datum/design/D)
|
||||
for(var/M in D.materials)
|
||||
if(materials[M] < D.materials[M])
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/check_build()
|
||||
if(!queue.len)
|
||||
progress = 0
|
||||
return
|
||||
var/datum/design/D = queue[1]
|
||||
if(!can_build(D))
|
||||
progress = 0
|
||||
return
|
||||
if(D.time > progress)
|
||||
return
|
||||
for(var/M in D.materials)
|
||||
materials[M] = max(0, materials[M] - D.materials[M] * mat_efficiency)
|
||||
if(D.build_path)
|
||||
var/obj/new_item = D.Fabricate(get_step(get_turf(src), src.dir), src)
|
||||
visible_message("\The [src] pings, indicating that \the [D] is complete.", "You hear a ping.")
|
||||
if(mat_efficiency != 1)
|
||||
if(new_item.matter && new_item.matter.len > 0)
|
||||
for(var/i in new_item.matter)
|
||||
new_item.matter[i] = new_item.matter[i] * mat_efficiency
|
||||
remove_from_queue(1)
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/get_queue_names()
|
||||
. = list()
|
||||
for(var/i = 2 to queue.len)
|
||||
var/datum/design/D = queue[i]
|
||||
. += D.name
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/get_build_options()
|
||||
. = list()
|
||||
for(var/i = 1 to files.known_designs.len)
|
||||
var/datum/design/D = files.known_designs[i]
|
||||
if(D.build_path && (D.build_type & PROSFAB))
|
||||
. += list(list("name" = D.name, "id" = i, "category" = D.category, "resourses" = get_design_resourses(D), "time" = get_design_time(D)))
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/get_design_resourses(var/datum/design/D)
|
||||
var/list/F = list()
|
||||
for(var/T in D.materials)
|
||||
F += "[capitalize(T)]: [D.materials[T] * mat_efficiency]"
|
||||
return english_list(F, and_text = ", ")
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/get_design_time(var/datum/design/D)
|
||||
return time2text(round(10 * D.time / speed), "mm:ss")
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/update_categories()
|
||||
categories = list()
|
||||
for(var/datum/design/D in files.known_designs)
|
||||
if(!D.build_path || !(D.build_type & PROSFAB))
|
||||
continue
|
||||
categories |= D.category
|
||||
if(!category || !(category in categories))
|
||||
category = categories[1]
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/get_materials()
|
||||
. = list()
|
||||
for(var/T in materials)
|
||||
. += list(list("mat" = capitalize(T), "amt" = materials[T]))
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/eject_materials(var/material, var/amount) // 0 amount = 0 means ejecting a full stack; -1 means eject everything
|
||||
var/recursive = amount == -1 ? 1 : 0
|
||||
var/matstring = lowertext(material)
|
||||
var/material/M = get_material_by_name(matstring)
|
||||
|
||||
var/obj/item/stack/material/S = M.place_sheet(get_turf(src))
|
||||
if(amount <= 0)
|
||||
amount = S.max_amount
|
||||
var/ejected = min(round(materials[matstring] / S.perunit), amount)
|
||||
S.amount = min(ejected, amount)
|
||||
if(S.amount <= 0)
|
||||
qdel(S)
|
||||
return
|
||||
materials[matstring] -= ejected * S.perunit
|
||||
if(recursive && materials[matstring] >= S.perunit)
|
||||
eject_materials(matstring, -1)
|
||||
update_busy()
|
||||
|
||||
/obj/machinery/pros_fabricator/proc/sync()
|
||||
sync_message = "Error: no console found."
|
||||
for(var/obj/machinery/computer/rdconsole/RDC in get_area_all_atoms(get_area(src)))
|
||||
if(!RDC.sync)
|
||||
continue
|
||||
for(var/datum/tech/T in RDC.files.known_tech)
|
||||
files.AddTech2Known(T)
|
||||
for(var/datum/design/D in RDC.files.known_designs)
|
||||
files.AddDesign2Known(D)
|
||||
files.RefreshResearch()
|
||||
sync_message = "Sync complete."
|
||||
update_categories()
|
||||
@@ -5,6 +5,7 @@
|
||||
var/buckle_lying = -1 //bed-like behavior, forces mob.lying = buckle_lying if != -1
|
||||
var/buckle_require_restraints = 0 //require people to be handcuffed before being able to buckle. eg: pipes
|
||||
var/mob/living/buckled_mob = null
|
||||
var/has_buckled
|
||||
|
||||
/obj/attack_hand(mob/living/user)
|
||||
. = ..()
|
||||
@@ -29,12 +30,17 @@
|
||||
/obj/proc/buckle_mob(mob/living/M)
|
||||
if(!can_buckle || !istype(M) || (M.loc != loc) || M.buckled || M.pinned.len || (buckle_require_restraints && !M.restrained()))
|
||||
return 0
|
||||
if(has_buckled) //Handles trying to buckle yourself to the chair when someone is on it
|
||||
M << "<span class='notice'>\The [src] already has someone buckled to it.</span>"
|
||||
return 0
|
||||
|
||||
M.buckled = src
|
||||
M.facing_dir = null
|
||||
M.set_dir(buckle_dir ? buckle_dir : dir)
|
||||
M.update_canmove()
|
||||
buckled_mob = M
|
||||
has_buckled = 1
|
||||
|
||||
post_buckle_mob(M)
|
||||
return 1
|
||||
|
||||
@@ -45,6 +51,7 @@
|
||||
buckled_mob.anchored = initial(buckled_mob.anchored)
|
||||
buckled_mob.update_canmove()
|
||||
buckled_mob = null
|
||||
has_buckled = 0
|
||||
|
||||
post_buckle_mob(.)
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ var/global/list/image/splatter_cache=list()
|
||||
var/base_icon = 'icons/effects/blood.dmi'
|
||||
blood_DNA = list()
|
||||
var/basecolor="#A10808" // Color when wet.
|
||||
var/synthblood = 0
|
||||
var/list/datum/disease2/disease/virus2 = list()
|
||||
var/amount = 5
|
||||
var/drytime
|
||||
@@ -61,9 +62,9 @@ var/global/list/image/splatter_cache=list()
|
||||
/obj/effect/decal/cleanable/blood/update_icon()
|
||||
if(basecolor == "rainbow") basecolor = "#[get_random_colour(1)]"
|
||||
color = basecolor
|
||||
if(basecolor == SYNTH_BLOOD_COLOUR)
|
||||
name = "oil"
|
||||
desc = "It's black and greasy."
|
||||
if(synthblood)
|
||||
name = "synthetic blood"
|
||||
desc = "It's quite greasy."
|
||||
else
|
||||
name = initial(name)
|
||||
desc = initial(desc)
|
||||
|
||||
@@ -453,6 +453,9 @@ var/list/global/slot_flags_enumeration = list(
|
||||
M.attack_log += "\[[time_stamp()]\]<font color='orange'> Attacked by [user.name] ([user.ckey]) with [src.name] (INTENT: [uppertext(user.a_intent)])</font>"
|
||||
msg_admin_attack("[user.name] ([user.ckey]) attacked [M.name] ([M.ckey]) with [src.name] (INTENT: [uppertext(user.a_intent)]) (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)") //BS12 EDIT ALG
|
||||
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
user.do_attack_animation(M)
|
||||
|
||||
src.add_fingerprint(user)
|
||||
//if((CLUMSY in user.mutations) && prob(50))
|
||||
// M = user
|
||||
@@ -633,6 +636,3 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
|
||||
/obj/item/proc/pwr_drain()
|
||||
return 0 // Process Kill
|
||||
|
||||
/obj/item/proc/resolve_attackby(atom/A, mob/source)
|
||||
return A.attackby(src,source)
|
||||
|
||||
|
||||
@@ -31,14 +31,17 @@
|
||||
last_used = world.time
|
||||
times_used = max(0,round(times_used)) //sanity
|
||||
|
||||
|
||||
/obj/item/device/flash/attack(mob/living/M as mob, mob/user as mob)
|
||||
//attack_as_weapon
|
||||
/obj/item/device/flash/attack(mob/living/M, mob/living/user, var/target_zone)
|
||||
if(!user || !M) return //sanity
|
||||
|
||||
M.attack_log += text("\[[time_stamp()]\] <font color='orange'>Has been flashed (attempt) with [src.name] by [user.name] ([user.ckey])</font>")
|
||||
user.attack_log += text("\[[time_stamp()]\] <font color='red'>Used the [src.name] to flash [M.name] ([M.ckey])</font>")
|
||||
msg_admin_attack("[user.name] ([user.ckey]) Used the [src.name] to flash [M.name] ([M.ckey]) (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)")
|
||||
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
user.do_attack_animation(M)
|
||||
|
||||
if(!clown_check(user)) return
|
||||
if(broken)
|
||||
user << "<span class='warning'>\The [src] is broken.</span>"
|
||||
@@ -117,6 +120,8 @@
|
||||
/obj/item/device/flash/attack_self(mob/living/carbon/user as mob, flag = 0, emp = 0)
|
||||
if(!user || !clown_check(user)) return
|
||||
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
|
||||
if(broken)
|
||||
user.show_message("<span class='warning'>The [src.name] is broken</span>", 2)
|
||||
return
|
||||
@@ -136,7 +141,6 @@
|
||||
else //can only use it 5 times a minute
|
||||
user.show_message("<span class='warning'>*click* *click*</span>", 2)
|
||||
return
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1)
|
||||
flick("flash2", src)
|
||||
if(user && isrobot(user))
|
||||
@@ -184,7 +188,8 @@
|
||||
icon_state = "sflash"
|
||||
origin_tech = list(TECH_MAGNET = 2, TECH_COMBAT = 1)
|
||||
|
||||
/obj/item/device/flash/synthetic/attack(mob/living/M as mob, mob/user as mob)
|
||||
//attack_as_weapon
|
||||
/obj/item/device/flash/synthetic/attack(mob/living/M, mob/living/user, var/target_zone)
|
||||
..()
|
||||
if(!broken)
|
||||
broken = 1
|
||||
|
||||
@@ -79,6 +79,8 @@
|
||||
user << "<span class='notice'>\The [M]'s pupils narrow slightly, but are still very dilated.</span>"
|
||||
else
|
||||
user << "<span class='notice'>\The [M]'s pupils narrow.</span>"
|
||||
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) //can be used offensively
|
||||
flick("flash", M.flash)
|
||||
else
|
||||
return ..()
|
||||
|
||||
@@ -10,11 +10,7 @@
|
||||
icon = 'icons/obj/decals.dmi'
|
||||
icon_state = "shock"
|
||||
|
||||
/obj/item/borg/stun/attack(var/mob/living/M, var/mob/living/silicon/robot/user)
|
||||
|
||||
if(!istype(M))
|
||||
return
|
||||
|
||||
/obj/item/borg/stun/apply_hit_effect(mob/living/M, mob/living/silicon/robot/user, var/hit_zone)
|
||||
// How the Hell.
|
||||
if(!istype(user))
|
||||
var/mob/living/temp = user
|
||||
@@ -23,18 +19,16 @@
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
M.attack_log += text("\[[time_stamp()]\] <font color='orange'>Has been attacked with [src.name] by [user.name] ([user.ckey])</font>")
|
||||
user.attack_log += text("\[[time_stamp()]\] <font color='red'>Used the [src.name] to attack [M.name] ([M.ckey])</font>")
|
||||
msg_admin_attack("[user.name] ([user.ckey]) used the [src.name] to attack [M.name] ([M.ckey]) (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)")
|
||||
user.visible_message("<span class='danger'>\The [user] has prodded \the [M] with \a [src]!</span>")
|
||||
|
||||
if(!user.cell || !user.cell.checked_use(1250)) //Slightly more than a baton.
|
||||
user.visible_message("<span class='danger'>\The [user] has prodded \the [M] with its arm!</span>")
|
||||
return
|
||||
|
||||
if (M.stuttering < 5)
|
||||
M.stuttering = 5
|
||||
M.stun_effect_act(0, 70, check_zone(user.zone_sel.selecting), src)
|
||||
user.visible_message("<span class='danger'>\The [user] has prodded \the [M] with \a [src]!</span>")
|
||||
playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
|
||||
|
||||
M.apply_effect(5, STUTTER)
|
||||
M.stun_effect_act(0, 70, check_zone(hit_zone), src)
|
||||
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
H.forcesay(hit_appends)
|
||||
|
||||
@@ -15,47 +15,37 @@
|
||||
|
||||
/obj/item/robot_parts/New(var/newloc, var/model)
|
||||
..(newloc)
|
||||
if(model_info && model)
|
||||
model_info = model
|
||||
var/datum/robolimb/R = all_robolimbs[model]
|
||||
if(R)
|
||||
name = "[R.company] [initial(name)]"
|
||||
desc = "[R.desc]"
|
||||
if(icon_state in icon_states(R.icon))
|
||||
icon = R.icon
|
||||
else
|
||||
name = "robot [initial(name)]"
|
||||
|
||||
/obj/item/robot_parts/l_arm
|
||||
name = "left arm"
|
||||
name = "cyborg left arm"
|
||||
desc = "A skeletal limb wrapped in pseudomuscles, with a low-conductivity case."
|
||||
icon_state = "l_arm"
|
||||
part = list(BP_L_ARM, BP_L_HAND)
|
||||
model_info = 1
|
||||
|
||||
/obj/item/robot_parts/r_arm
|
||||
name = "right arm"
|
||||
name = "cyborg right arm"
|
||||
desc = "A skeletal limb wrapped in pseudomuscles, with a low-conductivity case."
|
||||
icon_state = "r_arm"
|
||||
part = list(BP_R_ARM, BP_R_HAND)
|
||||
model_info = 1
|
||||
|
||||
/obj/item/robot_parts/l_leg
|
||||
name = "left leg"
|
||||
name = "cyborg left leg"
|
||||
desc = "A skeletal limb wrapped in pseudomuscles, with a low-conductivity case."
|
||||
icon_state = "l_leg"
|
||||
part = list(BP_L_LEG, BP_L_FOOT)
|
||||
model_info = 1
|
||||
|
||||
/obj/item/robot_parts/r_leg
|
||||
name = "right leg"
|
||||
name = "cyborg leg"
|
||||
desc = "A skeletal limb wrapped in pseudomuscles, with a low-conductivity case."
|
||||
icon_state = "r_leg"
|
||||
part = list(BP_R_LEG, BP_R_FOOT)
|
||||
model_info = 1
|
||||
|
||||
/obj/item/robot_parts/chest
|
||||
name = "chest"
|
||||
name = "cyborg chest"
|
||||
desc = "A heavily reinforced case containing cyborg logic boards, with space for a standard power cell."
|
||||
icon_state = "chest"
|
||||
part = list(BP_GROIN,BP_TORSO)
|
||||
@@ -63,7 +53,7 @@
|
||||
var/obj/item/weapon/cell/cell = null
|
||||
|
||||
/obj/item/robot_parts/head
|
||||
name = "head"
|
||||
name = "cyborg head"
|
||||
desc = "A standard reinforced braincase, with spine-plugged neural socket and sensor gimbals."
|
||||
icon_state = "head"
|
||||
part = list(BP_HEAD)
|
||||
|
||||
@@ -37,8 +37,13 @@
|
||||
user << "<span class='warning'>You can't apply [src] through [H.wear_suit]!</span>"
|
||||
return 1
|
||||
|
||||
if(affecting.robotic >= ORGAN_ROBOT)
|
||||
user << "<span class='warning'>This isn't useful at all on a robotic limb..</span>"
|
||||
if(affecting.robotic == ORGAN_ROBOT)
|
||||
user << "<span class='warning'>This isn't useful at all on a robotic limb.</span>"
|
||||
return 1
|
||||
|
||||
if(affecting.robotic >= ORGAN_LIFELIKE)
|
||||
user << "<span class='warning'>You apply the [src], but it seems to have no effect...</span>"
|
||||
use(1)
|
||||
return 1
|
||||
|
||||
H.UpdateDamageIcon()
|
||||
|
||||
@@ -27,19 +27,12 @@
|
||||
var/mob/living/carbon/human/H = M
|
||||
var/obj/item/organ/external/S = H.get_organ(user.zone_sel.selecting)
|
||||
|
||||
if(S.open == 1)
|
||||
if (S && (S.robotic >= ORGAN_ROBOT))
|
||||
if(S.get_damage())
|
||||
S.heal_damage(15, 15, robo_repair = 1)
|
||||
H.updatehealth()
|
||||
use(1)
|
||||
user.visible_message("<span class='notice'>\The [user] applies some nanite paste at[user != M ? " \the [M]'s" : " \the"][S.name] with \the [src].</span>",\
|
||||
"<span class='notice'>You apply some nanite paste at [user == M ? "your" : "[M]'s"] [S.name].</span>")
|
||||
else
|
||||
user << "<span class='notice'>Nothing to fix here.</span>"
|
||||
if (S && (S.robotic >= ORGAN_ROBOT))
|
||||
if(S.robo_repair(15, "omni", 0, src, user))
|
||||
use(1)
|
||||
user.visible_message("<span class='notice'>\The [user] applies some nanite paste on [user != M ? "\the [M]'s" : "their"] [S.name].</span>",\
|
||||
"<span class='notice'>You apply some nanite paste on [user == M ? "your" : "[M]'s"] [S.name].</span>")
|
||||
else
|
||||
if (can_operate(H))
|
||||
if (do_surgery(H,user,src))
|
||||
return
|
||||
else
|
||||
user << "<span class='notice'>Nothing to fix in here.</span>"
|
||||
|
||||
@@ -61,3 +61,14 @@ obj/item/weapon/circuitboard/rdserver
|
||||
/obj/item/weapon/stock_parts/manipulator = 1,
|
||||
/obj/item/weapon/stock_parts/micro_laser = 1,
|
||||
/obj/item/weapon/stock_parts/console_screen = 1)
|
||||
|
||||
/obj/item/weapon/circuitboard/prosthetics
|
||||
name = "Circuit board (Prosthetics Fabricator)"
|
||||
build_path = "/obj/machinery/pros_fabricator"
|
||||
board_type = "machine"
|
||||
origin_tech = list(TECH_DATA = 3, TECH_ENGINEERING = 3)
|
||||
req_components = list(
|
||||
/obj/item/weapon/stock_parts/matter_bin = 2,
|
||||
/obj/item/weapon/stock_parts/manipulator = 1,
|
||||
/obj/item/weapon/stock_parts/micro_laser = 1,
|
||||
/obj/item/weapon/stock_parts/console_screen = 1)
|
||||
@@ -53,9 +53,11 @@
|
||||
target.clean_blood()
|
||||
return
|
||||
|
||||
/obj/item/weapon/soap/attack(mob/target as mob, mob/user as mob)
|
||||
if(target && user && ishuman(target) && ishuman(user) && !target.stat && !user.stat && user.zone_sel &&user.zone_sel.selecting == O_MOUTH)
|
||||
//attack_as_weapon
|
||||
/obj/item/weapon/soap/attack(mob/living/target, mob/living/user, var/target_zone)
|
||||
if(target && user && ishuman(target) && ishuman(user) && !user.incapacitated() && user.zone_sel &&user.zone_sel.selecting == "mouth" )
|
||||
user.visible_message("<span class='danger'>\The [user] washes \the [target]'s mouth out with soap!</span>")
|
||||
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN) //prevent spam
|
||||
return
|
||||
..()
|
||||
|
||||
|
||||
@@ -114,6 +114,9 @@
|
||||
if(!do_after(user,50))
|
||||
return
|
||||
|
||||
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN)
|
||||
user.do_attack_animation(M)
|
||||
|
||||
M.visible_message("<span class='danger'>\The [M] has been injected with \the [src] by \the [user].</span>")
|
||||
|
||||
var/mob/living/carbon/human/H = M
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
if (ismob(target) || istype(target, /turf/unsimulated) || istype(target, /turf/simulated/shuttle) || istype(target, /obj/item/weapon/storage/) || istype(target, /obj/item/clothing/accessory/storage/) || istype(target, /obj/item/clothing/under))
|
||||
return
|
||||
user << "Planting explosives..."
|
||||
user.do_attack_animation(target)
|
||||
|
||||
if(do_after(user, 50) && in_range(user, target))
|
||||
user.drop_item()
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
qdel(src)
|
||||
|
||||
/obj/item/weapon/a_gift/attack_self(mob/M as mob)
|
||||
var/gift_type = pick(/obj/item/weapon/sord,
|
||||
var/gift_type = pick(
|
||||
/obj/item/weapon/storage/wallet,
|
||||
/obj/item/weapon/storage/photo_album,
|
||||
/obj/item/weapon/storage/box/snappops,
|
||||
@@ -80,7 +80,6 @@
|
||||
/obj/item/weapon/bikehorn,
|
||||
/obj/item/weapon/beach_ball,
|
||||
/obj/item/weapon/beach_ball/holoball,
|
||||
/obj/item/weapon/banhammer,
|
||||
/obj/item/toy/balloon,
|
||||
/obj/item/toy/blink,
|
||||
/obj/item/toy/crossbow,
|
||||
|
||||
@@ -74,6 +74,9 @@
|
||||
msg_admin_attack("[key_name(user)] attempted to handcuff [key_name(H)]")
|
||||
feedback_add_details("handcuffs","H")
|
||||
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
user.do_attack_animation(H)
|
||||
|
||||
user.visible_message("<span class='danger'>\The [user] has put [cuff_type] on \the [H]!</span>")
|
||||
|
||||
// Apply cuffs.
|
||||
@@ -166,4 +169,4 @@ var/last_chew = 0
|
||||
item_state = null
|
||||
icon = 'icons/obj/bureaucracy.dmi'
|
||||
breakouttime = 200
|
||||
cuff_type = "duct tape"
|
||||
cuff_type = "duct tape"
|
||||
|
||||
@@ -30,18 +30,18 @@
|
||||
if (!istype(M, /mob/living/carbon))
|
||||
return
|
||||
if (user && src.imp)
|
||||
for (var/mob/O in viewers(M, null))
|
||||
O.show_message("<span class='warning'>[user] is attemping to implant [M].</span>", 1)
|
||||
M.visible_message("<span class='warning'>[user] is attemping to implant [M].</span>")
|
||||
|
||||
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN)
|
||||
user.do_attack_animation(M)
|
||||
|
||||
var/turf/T1 = get_turf(M)
|
||||
if (T1 && ((M == user) || do_after(user, 50)))
|
||||
if(user && M && (get_turf(M) == T1) && src && src.imp)
|
||||
for (var/mob/O in viewers(M, null))
|
||||
O.show_message("<span class='warning'>[M] has been implanted by [user].</span>", 1)
|
||||
M.visible_message("<span class='warning'>[M] has been implanted by [user].</span>")
|
||||
|
||||
admin_attack_log(user, M, "Implanted using \the [src.name] ([src.imp.name])", "Implanted with \the [src.name] ([src.imp.name])", "used an implanter, [src.name] ([src.imp.name]), on")
|
||||
|
||||
user.show_message("<span class='warning'>You implanted the implant into [M].</span>")
|
||||
if(src.imp.implanted(M))
|
||||
src.imp.loc = M
|
||||
src.imp.imp_in = M
|
||||
|
||||
@@ -103,13 +103,6 @@
|
||||
return
|
||||
return ..()
|
||||
|
||||
/obj/item/weapon/material/kitchen/utensil/knife/attack(target as mob, mob/living/user as mob)
|
||||
if ((CLUMSY in user.mutations) && prob(50))
|
||||
user << "<span class='warning'>You somehow managed to cut yourself with \the [src].</span>"
|
||||
user.take_organ_damage(20)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/obj/item/weapon/material/kitchen/utensil/knife/plastic
|
||||
default_material = "plastic"
|
||||
|
||||
|
||||
@@ -64,9 +64,8 @@
|
||||
processing_objects -= src
|
||||
..()
|
||||
|
||||
/obj/item/weapon/material/attack()
|
||||
if(!..())
|
||||
return
|
||||
/obj/item/weapon/material/apply_hit_effect()
|
||||
..()
|
||||
if(!unbreakable)
|
||||
if(material.is_brittle())
|
||||
health = 0
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
icon = 'icons/obj/weapons.dmi'
|
||||
icon_state = "hoe"
|
||||
item_state = "hoe"
|
||||
flags = CONDUCT | NOBLUDGEON
|
||||
force_divisor = 0.25 // 5 with weight 20 (steel)
|
||||
thrown_force_divisor = 0.25 // as above
|
||||
w_class = 2
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
/obj/item/weapon/storage/box/emps/New()
|
||||
..()
|
||||
for(var/i = 1 to 5)
|
||||
new /obj/item/weapon/grenade/empgrenade(src)
|
||||
new /obj/item/weapon/grenade/empgrenade(src)
|
||||
|
||||
/obj/item/weapon/storage/box/empslite
|
||||
name = "box of low yield emp grenades"
|
||||
|
||||
@@ -103,73 +103,52 @@
|
||||
user << "<span class='warning'>[src] is out of charge.</span>"
|
||||
add_fingerprint(user)
|
||||
|
||||
|
||||
/obj/item/weapon/melee/baton/attack(mob/M, mob/user)
|
||||
if(status && (CLUMSY in user.mutations) && prob(50))
|
||||
user << "<span class='danger'>You accidentally hit yourself with the [src]!</span>"
|
||||
user.Weaken(30)
|
||||
deductcharge(hitcost)
|
||||
return
|
||||
return ..()
|
||||
|
||||
if(isrobot(M))
|
||||
..()
|
||||
return
|
||||
/obj/item/weapon/melee/baton/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone)
|
||||
if(isrobot(target))
|
||||
return ..()
|
||||
|
||||
var/agony = agonyforce
|
||||
var/stun = stunforce
|
||||
var/mob/living/L = M
|
||||
var/obj/item/organ/external/affecting = null
|
||||
if(ishuman(target))
|
||||
var/mob/living/carbon/human/H = target
|
||||
affecting = H.get_organ(hit_zone)
|
||||
|
||||
var/target_zone = check_zone(user.zone_sel.selecting)
|
||||
if(user.a_intent == I_HURT)
|
||||
if (!..()) //item/attack() does it's own messaging and logs
|
||||
return 0 // item/attack() will return 1 if they hit, 0 if they missed.
|
||||
. = ..()
|
||||
//whacking someone causes a much poorer electrical contact than deliberately prodding them.
|
||||
agony *= 0.5
|
||||
stun *= 0.5
|
||||
if(status) //Checks to see if the stunbaton is on.
|
||||
agony *= 0.5 //whacking someone causes a much poorer contact than prodding them.
|
||||
else if(!status)
|
||||
if(affecting)
|
||||
target.visible_message("<span class='warning'>[target] has been prodded in the [affecting.name] with [src] by [user]. Luckily it was off.</span>")
|
||||
else
|
||||
agony = 0 //Shouldn't really stun if it's off, should it?
|
||||
//we can't really extract the actual hit zone from ..(), unfortunately. Just act like they attacked the area they intended to.
|
||||
target.visible_message("<span class='warning'>[target] has been prodded with [src] by [user]. Luckily it was off.</span>")
|
||||
else
|
||||
//copied from human_defense.dm - human defence code should really be refactored some time.
|
||||
if (ishuman(L))
|
||||
user.lastattacked = L //are these used at all, if we have logs?
|
||||
L.lastattacker = user
|
||||
|
||||
if (user != L) // Attacking yourself can't miss
|
||||
target_zone = get_zone_with_miss_chance(user.zone_sel.selecting, L)
|
||||
|
||||
if(!target_zone)
|
||||
L.visible_message("<span class='danger'>\The [user] misses [L] with \the [src]!</span>")
|
||||
return 0
|
||||
|
||||
var/mob/living/carbon/human/H = L
|
||||
var/obj/item/organ/external/affecting = H.get_organ(target_zone)
|
||||
if (affecting)
|
||||
if(!status)
|
||||
L.visible_message("<span class='warning'>[L] has been prodded in the [affecting.name] with [src] by [user]. Luckily it was off.</span>")
|
||||
return 1
|
||||
else
|
||||
H.visible_message("<span class='danger'>[L] has been prodded in the [affecting.name] with [src] by [user]!</span>")
|
||||
if(affecting)
|
||||
target.visible_message("<span class='danger'>[target] has been prodded in the [affecting.name] with [src] by [user]!</span>")
|
||||
else
|
||||
if(!status)
|
||||
L.visible_message("<span class='warning'>[L] has been prodded with [src] by [user]. Luckily it was off.</span>")
|
||||
return 1
|
||||
else
|
||||
L.visible_message("<span class='danger'>[L] has been prodded with [src] by [user]!</span>")
|
||||
target.visible_message("<span class='danger'>[target] has been prodded with [src] by [user]!</span>")
|
||||
playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
|
||||
|
||||
//stun effects
|
||||
L.stun_effect_act(stun, agony, target_zone, src)
|
||||
if(status)
|
||||
target.stun_effect_act(stun, agony, hit_zone, src)
|
||||
msg_admin_attack("[key_name(user)] stunned [key_name(target)] with the [src].")
|
||||
|
||||
playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
|
||||
msg_admin_attack("[key_name(user)] stunned [key_name(L)] with the [src].")
|
||||
deductcharge(hitcost)
|
||||
|
||||
deductcharge(hitcost)
|
||||
|
||||
if(ishuman(L))
|
||||
var/mob/living/carbon/human/H = L
|
||||
H.forcesay(hit_appends)
|
||||
|
||||
return 1
|
||||
if(ishuman(target))
|
||||
var/mob/living/carbon/human/H = target
|
||||
H.forcesay(hit_appends)
|
||||
|
||||
/obj/item/weapon/melee/baton/emp_act(severity)
|
||||
if(bcell)
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
/* Weapons
|
||||
* Contains:
|
||||
* Banhammer
|
||||
* Sword
|
||||
* Classic Baton
|
||||
*/
|
||||
|
||||
/*
|
||||
* Banhammer
|
||||
*/
|
||||
/obj/item/weapon/banhammer/attack(mob/M as mob, mob/user as mob)
|
||||
M << "<font color='red'><b> You have been banned FOR NO REISIN by [user]</b></font>"
|
||||
user << "<font color='red'> You have <b>BANNED</b> [M]</font>"
|
||||
|
||||
/*
|
||||
/*
|
||||
* Classic Baton
|
||||
*/
|
||||
/obj/item/weapon/melee/classic_baton
|
||||
@@ -34,33 +26,7 @@
|
||||
else
|
||||
user.take_organ_damage(2*force)
|
||||
return
|
||||
/*this is already called in ..()
|
||||
src.add_fingerprint(user)
|
||||
M.attack_log += text("\[[time_stamp()]\] <font color='orange'>Has been attacked with [src.name] by [user.name] ([user.ckey])</font>")
|
||||
user.attack_log += text("\[[time_stamp()]\] <font color='red'>Used the [src.name] to attack [M.name] ([M.ckey])</font>")
|
||||
|
||||
log_attack("<font color='red'>[user.name] ([user.ckey]) attacked [M.name] ([M.ckey]) with [src.name] (INTENT: [uppertext(user.a_intent)])</font>")
|
||||
*/
|
||||
if (user.a_intent == I_HURT)
|
||||
if(!..()) return
|
||||
//playsound(src.loc, "swing_hit", 50, 1, -1)
|
||||
if (M.stuttering < 8 && (!(HULK in M.mutations)) /*&& (!istype(H:wear_suit, /obj/item/clothing/suit/judgerobe))*/)
|
||||
M.stuttering = 8
|
||||
M.Stun(8)
|
||||
M.Weaken(8)
|
||||
for(var/mob/O in viewers(M))
|
||||
if (O.client) O.show_message("<span class='danger'>\The [M] has been beaten with \the [src] by [user]!</span>", 1, "<span class='warning'>You hear someone fall</span>", 2)
|
||||
else
|
||||
playsound(src.loc, 'sound/weapons/Genhit.ogg', 50, 1, -1)
|
||||
M.Stun(5)
|
||||
M.Weaken(5)
|
||||
M.attack_log += text("\[[time_stamp()]\] <font color='orange'>Has been attacked with [src.name] by [user.name] ([user.ckey])</font>")
|
||||
user.attack_log += text("\[[time_stamp()]\] <font color='red'>Used the [src.name] to attack [M.name] ([M.ckey])</font>")
|
||||
msg_admin_attack("[key_name(user)] attacked [key_name(user)] with [src.name] (INTENT: [uppertext(user.a_intent)])")
|
||||
src.add_fingerprint(user)
|
||||
|
||||
for(var/mob/O in viewers(M))
|
||||
if (O.client) O.show_message("<span class='danger'>\The [M] has been stunned with \the [src] by [user]!</span>", 1, "<span class='warning'>You hear someone fall</span>", 2)
|
||||
return ..()
|
||||
|
||||
//Telescopic baton
|
||||
/obj/item/weapon/melee/telebaton
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
item_state = "cutters_yellow"
|
||||
|
||||
/obj/item/weapon/wirecutters/attack(mob/living/carbon/C as mob, mob/user as mob)
|
||||
if((C.handcuffed) && (istype(C.handcuffed, /obj/item/weapon/handcuffs/cable)))
|
||||
if(user.a_intent == I_HELP && (C.handcuffed) && (istype(C.handcuffed, /obj/item/weapon/handcuffs/cable)))
|
||||
usr.visible_message("\The [usr] cuts \the [C]'s restraints with \the [src]!",\
|
||||
"You cut \the [C]'s restraints with \the [src]!",\
|
||||
"You hear cable being cut.")
|
||||
|
||||
@@ -1,18 +1,3 @@
|
||||
/obj/item/weapon/banhammer
|
||||
desc = "banhammer"
|
||||
name = "banhammer"
|
||||
icon = 'icons/obj/items.dmi'
|
||||
icon_state = "toyhammer"
|
||||
slot_flags = SLOT_BELT
|
||||
throwforce = 0
|
||||
w_class = 2.0
|
||||
throw_speed = 7
|
||||
throw_range = 15
|
||||
attack_verb = list("banned")
|
||||
|
||||
suicide_act(mob/user)
|
||||
viewers(user) << "<span class='danger'>[user] is hitting \himself with the [src.name]! It looks like \he's trying to ban \himself from life.</span>"
|
||||
return (BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS)
|
||||
|
||||
/obj/item/weapon/nullrod
|
||||
name = "null rod"
|
||||
@@ -37,6 +22,9 @@
|
||||
|
||||
msg_admin_attack("[user.name] ([user.ckey]) attacked [M.name] ([M.ckey]) with [src.name] (INTENT: [uppertext(user.a_intent)]) (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)")
|
||||
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
user.do_attack_animation(M)
|
||||
|
||||
if (!(istype(user, /mob/living/carbon/human) || ticker) && ticker.mode.name != "monkey")
|
||||
user << "<span class='danger'>You don't have the dexterity to do this!</span>"
|
||||
return
|
||||
@@ -68,27 +56,6 @@
|
||||
user << "<span class='notice'>You hit the floor with the [src].</span>"
|
||||
call(/obj/effect/rune/proc/revealrunes)(src)
|
||||
|
||||
/obj/item/weapon/sord
|
||||
name = "\improper SORD"
|
||||
desc = "This thing is so unspeakably shitty you are having a hard time even holding it."
|
||||
icon_state = "sord"
|
||||
item_state = "sord"
|
||||
slot_flags = SLOT_BELT
|
||||
force = 2
|
||||
throwforce = 1
|
||||
sharp = 1
|
||||
edge = 1
|
||||
w_class = 3
|
||||
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
|
||||
|
||||
suicide_act(mob/user)
|
||||
viewers(user) << "<span class='danger'>[user] is impaling \himself with the [src.name]! It looks like \he's trying to commit suicide.</span>"
|
||||
return(BRUTELOSS)
|
||||
|
||||
/obj/item/weapon/sord/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
|
||||
playsound(loc, 'sound/weapons/bladeslice.ogg', 50, 1, -1)
|
||||
return ..()
|
||||
|
||||
/obj/item/weapon/energy_net
|
||||
name = "energy net"
|
||||
desc = "It's a net made of green energy."
|
||||
|
||||
@@ -101,6 +101,78 @@ LINEN BINS
|
||||
icon_state = "sheetbrown"
|
||||
item_state = "sheetbrown"
|
||||
|
||||
/obj/item/weapon/bedsheet/ian
|
||||
icon_state = "sheetian"
|
||||
item_state = "bedsheet"
|
||||
|
||||
/obj/item/weapon/bedsheet/double
|
||||
icon_state = "doublesheet"
|
||||
item_state = "bedsheet"
|
||||
|
||||
/obj/item/weapon/bedsheet/bluedouble
|
||||
icon_state = "doublesheetblue"
|
||||
item_state = "sheetblue"
|
||||
|
||||
/obj/item/weapon/bedsheet/greendouble
|
||||
icon_state = "doublesheetgreen"
|
||||
item_state = "sheetgreen"
|
||||
|
||||
/obj/item/weapon/bedsheet/orangedouble
|
||||
icon_state = "doublesheetorange"
|
||||
item_state = "sheetorange"
|
||||
|
||||
/obj/item/weapon/bedsheet/purpledouble
|
||||
icon_state = "doublesheetpurple"
|
||||
item_state = "sheetpurple"
|
||||
|
||||
/obj/item/weapon/bedsheet/doublerainbow //all the way across the sky.
|
||||
icon_state = "doublesheetrainbow"
|
||||
item_state = "sheetrainbow"
|
||||
|
||||
/obj/item/weapon/bedsheet/doublered
|
||||
icon_state = "doublesheetred"
|
||||
item_state = "sheetred"
|
||||
|
||||
/obj/item/weapon/bedsheet/doubleyellow
|
||||
icon_state = "doublesheetyellow"
|
||||
item_state = "sheetyellow"
|
||||
|
||||
/obj/item/weapon/bedsheet/doublemime
|
||||
icon_state = "doublesheetmime"
|
||||
item_state = "sheetmime"
|
||||
|
||||
/obj/item/weapon/bedsheet/doubleclown
|
||||
icon_state = "doublesheetclown"
|
||||
item_state = "sheetclown"
|
||||
|
||||
/obj/item/weapon/bedsheet/doublecaptain
|
||||
icon_state = "doublesheetcaptain"
|
||||
item_state = "sheetcaptain"
|
||||
|
||||
/obj/item/weapon/bedsheet/doublerd
|
||||
icon_state = "doublesheetrd"
|
||||
item_state = "sheetrd"
|
||||
|
||||
/obj/item/weapon/bedsheet/doublehos
|
||||
icon_state = "doublesheethos"
|
||||
item_state = "sheethos"
|
||||
|
||||
/obj/item/weapon/bedsheet/doublehop
|
||||
icon_state = "doublesheethop"
|
||||
item_state = "sheethop"
|
||||
|
||||
/obj/item/weapon/bedsheet/doublece
|
||||
icon_state = "doublesheetce"
|
||||
item_state = "sheetce"
|
||||
|
||||
/obj/item/weapon/bedsheet/doublebrown
|
||||
icon_state = "doublesheetbrown"
|
||||
item_state = "sheetbrown"
|
||||
|
||||
/obj/item/weapon/bedsheet/doubleian
|
||||
icon_state = "doublesheetian"
|
||||
item_state = "bedsheet"
|
||||
|
||||
|
||||
/obj/structure/bedsheetbin
|
||||
name = "linen bin"
|
||||
|
||||
@@ -129,6 +129,9 @@
|
||||
else if(istype(W, /obj/item/weapon/grab))
|
||||
var/obj/item/weapon/grab/G = W
|
||||
var/mob/living/affecting = G.affecting
|
||||
if(has_buckled) //Handles trying to buckle someone else to a chair when someone else is on it
|
||||
user << "<span class='notice'>\The [src] already has someone buckled to it.</span>"
|
||||
return
|
||||
user.visible_message("<span class='notice'>[user] attempts to buckle [affecting] into \the [src]!</span>")
|
||||
if(do_after(user, 20))
|
||||
affecting.loc = loc
|
||||
@@ -176,6 +179,19 @@
|
||||
/obj/structure/bed/alien/New(var/newloc)
|
||||
..(newloc,"resin")
|
||||
|
||||
/obj/structure/bed/double
|
||||
name = "double bed"
|
||||
icon_state = "doublebed"
|
||||
base_icon = "doublebed"
|
||||
|
||||
/obj/structure/bed/double/post_buckle_mob(mob/living/M as mob)
|
||||
if(M == buckled_mob)
|
||||
M.pixel_y = 13
|
||||
M.old_y = 13
|
||||
else
|
||||
M.pixel_y = 0
|
||||
M.old_y = 0
|
||||
|
||||
/*
|
||||
* Roller beds
|
||||
*/
|
||||
|
||||
@@ -72,6 +72,11 @@ var/global/list/stool_cache = list() //haha stool
|
||||
/obj/item/weapon/stool/attack(mob/M as mob, mob/user as mob)
|
||||
if (prob(5) && istype(M,/mob/living))
|
||||
user.visible_message("<span class='danger'>[user] breaks [src] over [M]'s back!</span>")
|
||||
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
||||
user.do_attack_animation(M)
|
||||
|
||||
user.drop_from_inventory(src)
|
||||
|
||||
user.remove_from_mob(src)
|
||||
dismantle()
|
||||
qdel(src)
|
||||
|
||||
@@ -5,39 +5,83 @@
|
||||
icon_state = "cabinet_closed"
|
||||
density = 1
|
||||
|
||||
/obj/structure/undies_wardrobe/attack_hand(mob/user as mob)
|
||||
src.add_fingerprint(user)
|
||||
var/mob/living/carbon/human/H = user
|
||||
if(!ishuman(user) || (H.species && !(H.species.appearance_flags & HAS_UNDERWEAR)))
|
||||
/obj/structure/undies_wardrobe/attack_hand(var/mob/user)
|
||||
if(!human_who_can_use_underwear(user))
|
||||
user << "<span class='warning'>Sadly there's nothing in here for you to wear.</span>"
|
||||
return 0
|
||||
return
|
||||
interact(user)
|
||||
|
||||
var/utype = alert("Which section do you want to pick from?",,"Underwear", "Undershirts", "Socks",)
|
||||
var/list/selection
|
||||
switch(utype)
|
||||
if("Underwear")
|
||||
utype = alert("Which section do you want to pick from?",, "Top", "Bottom",)
|
||||
switch(utype)
|
||||
if("Top")
|
||||
selection = underwear_top_t
|
||||
if("Bottom")
|
||||
selection = underwear_bottom_t
|
||||
if("Undershirts")
|
||||
selection = undershirt_t
|
||||
if("Socks")
|
||||
selection = socks_t
|
||||
var/pick = input("Select the style") as null|anything in selection
|
||||
if(pick)
|
||||
if(get_dist(src,user) > 1)
|
||||
/obj/structure/undies_wardrobe/interact(var/mob/living/carbon/human/H)
|
||||
var/dat = list()
|
||||
dat += "<b>Underwear:</b><br>"
|
||||
for(var/datum/category_group/underwear/UWC in global_underwear.categories)
|
||||
var/datum/category_item/underwear/UWI = H.all_underwear[UWC.name]
|
||||
var/item_name = UWI ? UWI.name : "None"
|
||||
dat += "[UWC.name]: <a href='?src=\ref[src];change_underwear=[UWC.name]'>[item_name]</a>"
|
||||
if(UWI)
|
||||
for(var/datum/gear_tweak/gt in UWI.tweaks)
|
||||
dat += " <a href='?src=\ref[src];underwear=[UWC.name];tweak=\ref[gt]'>[gt.get_contents(get_metadata(H, UWC.name, gt))]</a>"
|
||||
dat += " <a href='?src=\ref[src];remove_underwear=[UWC.name]'>(Remove)</a><br>"
|
||||
|
||||
dat = jointext(dat,null)
|
||||
H << browse(dat, "window=wardrobe;size=400x200")
|
||||
|
||||
/obj/structure/undies_wardrobe/proc/get_metadata(var/mob/living/carbon/human/H, var/underwear_category, var/datum/gear_tweak/gt)
|
||||
var/metadata = H.all_underwear_metadata[underwear_category]
|
||||
if(!metadata)
|
||||
metadata = list()
|
||||
H.all_underwear_metadata[underwear_category] = metadata
|
||||
|
||||
var/tweak_data = metadata["[gt]"]
|
||||
if(!tweak_data)
|
||||
tweak_data = gt.get_default()
|
||||
metadata["[gt]"] = tweak_data
|
||||
return tweak_data
|
||||
|
||||
/obj/structure/undies_wardrobe/proc/set_metadata(var/mob/living/carbon/human/H, var/underwear_category, var/datum/gear_tweak/gt, var/new_metadata)
|
||||
var/list/metadata = H.all_underwear_metadata[underwear_category]
|
||||
metadata["[gt]"] = new_metadata
|
||||
|
||||
/obj/structure/undies_wardrobe/proc/human_who_can_use_underwear(var/mob/living/carbon/human/H)
|
||||
if(!istype(H) || !H.species || !(H.species.appearance_flags & HAS_UNDERWEAR))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/structure/undies_wardrobe/CanUseTopic(var/user)
|
||||
if(!human_who_can_use_underwear(user))
|
||||
return STATUS_CLOSE
|
||||
|
||||
return ..()
|
||||
|
||||
/obj/structure/undies_wardrobe/Topic(href, href_list, state)
|
||||
if(..())
|
||||
return TRUE
|
||||
|
||||
var/mob/living/carbon/human/H = usr
|
||||
if(href_list["remove_underwear"])
|
||||
if(href_list["remove_underwear"] in H.all_underwear)
|
||||
H.all_underwear -= href_list["remove_underwear"]
|
||||
. = TRUE
|
||||
else if(href_list["change_underwear"])
|
||||
var/datum/category_group/underwear/UWC = global_underwear.categories_by_name[href_list["change_underwear"]]
|
||||
if(!UWC)
|
||||
return
|
||||
if(utype == "Undershirts")
|
||||
H.undershirt = selection[pick]
|
||||
else if(utype == "Socks")
|
||||
H.socks = selection[pick]
|
||||
else if(utype == "Top")
|
||||
H.underwear_top = selection[pick]
|
||||
else
|
||||
H.underwear_bottom = selection[pick]
|
||||
H.update_body(1)
|
||||
var/datum/category_item/underwear/selected_underwear = input(H, "Choose underwear:", "Choose underwear", H.all_underwear[UWC.name]) as null|anything in UWC.items
|
||||
if(selected_underwear && CanUseTopic(H, default_state))
|
||||
H.all_underwear[UWC.name] = selected_underwear
|
||||
. = TRUE
|
||||
else if(href_list["underwear"] && href_list["tweak"])
|
||||
var/underwear = href_list["underwear"]
|
||||
if(!(underwear in H.all_underwear))
|
||||
return
|
||||
var/datum/gear_tweak/gt = locate(href_list["tweak"])
|
||||
if(!gt)
|
||||
return
|
||||
var/new_metadata = gt.get_metadata(usr, get_metadata(H, underwear, gt), "Wardrobe Underwear Selection")
|
||||
if(new_metadata)
|
||||
set_metadata(H, underwear, gt, new_metadata)
|
||||
. = TRUE
|
||||
|
||||
return 1
|
||||
if(.)
|
||||
H.update_underwear()
|
||||
interact(H)
|
||||
@@ -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
|
||||
|
||||
@@ -118,7 +118,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.
|
||||
|
||||
|
||||
@@ -234,9 +234,9 @@ Ccomp's first proc.
|
||||
|
||||
var/list/mobs = list()
|
||||
var/list/ghosts = list()
|
||||
var/list/sortmob = sortAtom(mob_list) // get the mob list.
|
||||
var/list/sortmob = sortAtom(mob_list) // get the mob list.
|
||||
var/any=0
|
||||
for(var/mob/observer/dead/M in sortmob)
|
||||
for(var/mob/observer/dead/M in sortmob)
|
||||
mobs.Add(M) //filter it where it's only ghosts
|
||||
any = 1 //if no ghosts show up, any will just be 0
|
||||
if(!any)
|
||||
@@ -388,7 +388,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
|
||||
else
|
||||
new_character.gender = pick(MALE,FEMALE)
|
||||
var/datum/preferences/A = new()
|
||||
A.randomize_appearance_for(new_character)
|
||||
A.randomize_appearance_and_body_for(new_character)
|
||||
new_character.real_name = G_found.real_name
|
||||
|
||||
if(!new_character.real_name)
|
||||
|
||||
@@ -80,7 +80,7 @@ datum/preferences/proc/set_biological_gender(var/gender)
|
||||
var/new_gender = input(user, "Choose your character's biological gender:", "Character Preference", pref.biological_gender) as null|anything in get_genders()
|
||||
if(new_gender && CanUseTopic(user))
|
||||
pref.set_biological_gender(new_gender)
|
||||
return TOPIC_REFRESH
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if(href_list["id_gender"])
|
||||
var/new_gender = input(user, "Choose your character's identifying gender:", "Character Preference", pref.identifying_gender) as null|anything in all_genders_define_list
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user