-"}
-
- /**
- * Return the HTML for this UI
- *
- * @return string HTML for the UI
- */
-/datum/nanoui/proc/get_html()
- return {"
- [get_header()]
- [content]
- [get_footer()]
+
"}
/**
@@ -333,7 +407,8 @@ nanoui is used to open and update nano browser uis
*
* @return nothing
*/
-/datum/nanoui/proc/open()
+/datum/nanoui/proc/open()
+
var/window_size = ""
if (width && height)
window_size = "size=[width]x[height];"
@@ -377,9 +452,10 @@ nanoui is used to open and update nano browser uis
if (status == STATUS_DISABLED && !force_push)
return // Cannot update UI, no visibility
- data = add_default_data(data)
+ var/list/send_data = get_send_data(data)
+
//user << list2json(data) // used for debugging
- user << output(list2params(list(list2json(data))),"[window_id].browser:receiveUpdateData")
+ user << output(list2params(list(list2json(send_data))),"[window_id].browser:receiveUpdateData")
/**
* This Topic() proc is called whenever a user clicks on a link within a Nano UI
@@ -392,8 +468,18 @@ nanoui is used to open and update nano browser uis
update_status(0) // update the status
if (status != STATUS_INTERACTIVE || user != usr) // If UI is not interactive or usr calling Topic is not the UI user
return
+
+ // This is used to toggle the nano map ui
+ var/map_update = 0
+ if(href_list["showMap"])
+ set_show_map(text2num(href_list["showMap"]))
+ map_update = 1
+
+ if(href_list["mapZLevel"])
+ set_map_z_level(text2num(href_list["mapZLevel"]))
+ map_update = 1
- if (src_object && src_object.Topic(href, href_list))
+ if ((src_object && src_object.Topic(href, href_list)) || map_update)
nanomanager.update_uis(src_object) // update all UIs attached to src_object
/**
@@ -410,7 +496,15 @@ nanoui is used to open and update nano browser uis
return
if (status && (update || is_auto_updating))
- src_object.ui_interact(user, ui_key, src) // Update the UI (update_status() is called whenever a UI is updated)
+ update() // Update the UI (update_status() is called whenever a UI is updated)
else
update_status(1) // Not updating UI, so lets check here if status has changed
+
+ /**
+ * Update the UI
+ *
+ * @return nothing
+ */
+/datum/nanoui/proc/update(var/force_open = 0)
+ src_object.ui_interact(user, ui_key, src, force_open)
diff --git a/code/modules/organs/blood.dm b/code/modules/organs/blood.dm
index e9f1563c..3a691d9d 100644
--- a/code/modules/organs/blood.dm
+++ b/code/modules/organs/blood.dm
@@ -63,7 +63,7 @@ var/const/BLOOD_VOLUME_SURVIVE = 122
// Damaged heart virtually reduces the blood volume, as the blood isn't
// being pumped properly anymore.
- var/datum/organ/internal/heart/heart = internal_organs["heart"]
+ var/datum/organ/internal/heart/heart = internal_organs_by_name["heart"]
if(heart.damage > 1 && heart.damage < heart.min_bruised_damage)
blood_volume *= 0.8
@@ -127,14 +127,12 @@ var/const/BLOOD_VOLUME_SURVIVE = 122
if(!(temp.status & ORGAN_BLEEDING) || temp.status & ORGAN_ROBOT)
continue
for(var/datum/wound/W in temp.wounds) if(W.bleeding())
- blood_max += W.damage / 4
- if(temp.status & ORGAN_DESTROYED && !(temp.status & ORGAN_GAUZED) && !temp.amputated)
- blood_max += 20 //Yer missing a fucking limb.
+ blood_max += W.damage / 40
if (temp.open)
blood_max += 2 //Yer stomach is cut open
drip(blood_max)
-//Makes a blood drop, leaking certain amount of blood from the mob
+//Makes a blood drop, leaking amt units of blood from the mob
/mob/living/carbon/human/proc/drip(var/amt as num)
if(species && species.flags & NO_BLOOD) //TODO: Make drips come from the reagents instead.
@@ -143,12 +141,11 @@ var/const/BLOOD_VOLUME_SURVIVE = 122
if(!amt)
return
- var/amm = 0.1 * amt
var/turf/T = get_turf(src)
var/list/obj/effect/decal/cleanable/blood/drip/nums = list()
var/list/iconL = list("1","2","3","4","5")
- vessel.remove_reagent("blood",amm)
+ vessel.remove_reagent("blood",amt)
for(var/obj/effect/decal/cleanable/blood/drip/G in T)
nums += G
diff --git a/code/modules/organs/organ.dm b/code/modules/organs/organ.dm
index 701f6cf7..f0c28d87 100644
--- a/code/modules/organs/organ.dm
+++ b/code/modules/organs/organ.dm
@@ -5,6 +5,9 @@
var/list/datum/autopsy_data/autopsy_data = list()
var/list/trace_chemicals = list() // traces of chemicals in the organ,
// links chemical IDs to number of ticks for which they'll stay in the blood
+
+ var/germ_level = 0 // INTERNAL germs inside the organ, this is BAD if it's greater than INFECTION_LEVEL_ONE
+
proc/process()
return 0
@@ -14,6 +17,20 @@
/datum/organ/proc/get_icon(var/icon/race_icon, var/icon/deform_icon)
return icon('icons/mob/human.dmi',"blank")
+//Germs
+/datum/organ/proc/handle_antibiotics()
+ var/antibiotics = owner.reagents.get_reagent_amount("spaceacillin")
+
+ if (!germ_level || antibiotics < 5)
+ return
+
+ if (germ_level < INFECTION_LEVEL_ONE)
+ germ_level = 0 //cure instantly
+ else if (germ_level < INFECTION_LEVEL_TWO)
+ germ_level -= 6 //at germ_level == 500, this should cure the infection in a minute
+ else
+ germ_level -= 2 //at germ_level == 1000, this will cure the infection in 5 minutes
+
//Handles chem traces
/mob/living/carbon/human/proc/handle_trace_chems()
//New are added for reagents to random organs.
@@ -46,13 +63,18 @@
if(damage_this_tick > last_dam)
force_process = 1
last_dam = damage_this_tick
- if(!force_process && !bad_external_organs.len)
- return
if(force_process)
bad_external_organs.Cut()
for(var/datum/organ/external/Ex in organs)
bad_external_organs += Ex
+ //processing internal organs is pretty cheap, do that first.
+ for(var/datum/organ/internal/I in internal_organs)
+ I.process()
+
+ if(!force_process && !bad_external_organs.len)
+ return
+
for(var/datum/organ/external/E in bad_external_organs)
if(!E)
continue
@@ -62,63 +84,22 @@
else
E.process()
number_wounds += E.number_wounds
- //Robotic limb malfunctions
- var/malfunction = 0
- if (E.status & ORGAN_ROBOT && prob(E.brute_dam + E.burn_dam))
- malfunction = 1
-
- //Broken limbs hurt too
- var/broken = 0
- if(E.status & ORGAN_BROKEN && !(E.status & ORGAN_SPLINTED) )
- broken = 1
+ if (!lying && world.time - l_move_time < 15)
//Moving around with fractured ribs won't do you any good
- if (broken && E.internal_organs && prob(15))
- if (!lying && world.timeofday - l_move_time < 15)
+ if (E.is_broken() && E.internal_organs && prob(15))
var/datum/organ/internal/I = pick(E.internal_organs)
custom_pain("You feel broken bones moving in your [E.display_name]!", 1)
I.take_damage(rand(3,5))
- //Special effects for limbs.
- if(E.name in list("l_hand","l_arm","r_hand","r_arm"))
- var/obj/item/c_hand //Getting what's in this hand
- var/hand
- if(E.name == "l_hand" || E.name == "l_arm")
- c_hand = l_hand
- hand = "left hand"
- if(E.name == "r_hand" || E.name == "r_arm")
- c_hand = r_hand
- hand = "right hand"
- if (c_hand)
+ //Moving makes open wounds get infected much faster
+ if (E.wounds.len)
+ for(var/datum/wound/W in E.wounds)
+ if (W.infection_check())
+ W.germ_level += 1
- if(broken)
- if(istype(c_hand, /obj/item/weapon/gun/offhand))
- var/obj/item/weapon/gun/O = get_inactive_hand()
- var/obj/item/weapon/gun/offhand/OH = c_hand
- O.unwield()
- OH.unwield()
- else
- u_equip(c_hand)
- var/emote_scream = pick("screams in pain and", "let's out a sharp hiss and", "cries out and")
- emote("me", 1, "[(species && species.flags & NO_PAIN) ? "" : emote_scream ] drops what they were holding in their [hand]!")
- if(malfunction)
- if(istype(c_hand, /obj/item/weapon/gun/offhand))
- var/obj/item/weapon/gun/O = get_inactive_hand()
- var/obj/item/weapon/gun/offhand/OH = c_hand
- O.unwield()
- OH.unwield()
- else
- u_equip(c_hand)
- emote("me", 1, "drops what they were holding, their [hand] malfunctioning!")
- var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread()
- spark_system.set_up(5, 0, src)
- spark_system.attach(src)
- spark_system.start()
- spawn(10)
- del(spark_system)
-
- else if(E.name in list("l_leg","l_foot","r_leg","r_foot") && !lying)
- if (!E.is_usable() || malfunction || (broken && !(E.status & ORGAN_SPLINTED)))
+ if(E.name in list("l_leg","l_foot","r_leg","r_foot") && !lying)
+ if (!E.is_usable() || E.is_malfunctioning() || (E.is_broken() && !(E.status & ORGAN_SPLINTED)))
leg_tally-- // let it fail even if just foot&leg
// standing is poor
diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm
index 58751c0a..79bbe771 100644
--- a/code/modules/organs/organ_external.dm
+++ b/code/modules/organs/organ_external.dm
@@ -32,6 +32,7 @@
var/damage_msg = "\red You feel an intense pain"
var/broken_description
+ var/vital //Lose a vital limb, die immediately.
var/status = 0
var/open = 0
var/stage = 0
@@ -40,8 +41,6 @@
var/obj/item/hidden = null
var/list/implants = list()
- // INTERNAL germs inside the organ, this is BAD if it's greater 0
- var/germ_level = 0
// how often wounds should be updated, a higher number means less often
var/wound_update_accuracy = 1
@@ -92,15 +91,8 @@
brute *= brmod //~2/3 damage for ROBOLIMBS
burn *= bumod //~2/3 damage for ROBOLIMBS
- //If limb took enough damage, try to cut or tear it off
- if(body_part != UPPER_TORSO && body_part != LOWER_TORSO) //as hilarious as it is, getting hit on the chest too much shouldn't effectively gib you.
- if(config.limbs_can_break && brute_dam >= max_damage * config.organ_health_multiplier)
- if( (edge && prob(5 * brute)) || (brute > 20 && prob(2 * brute)) )
- droplimb(1)
- return
-
// High brute damage or sharp objects may damage internal organs
- if(internal_organs != null) if( (sharp && brute >= 5) || brute >= 10) if(prob(5))
+ if(internal_organs && ( (sharp && brute >= 5) || brute >= 10) && prob(5))
// Damage an internal organ
var/datum/organ/internal/I = pick(internal_organs)
I.take_damage(brute / 2)
@@ -163,6 +155,14 @@
// sync the organ's damage with its wounds
src.update_damages()
+
+ //If limb took enough damage, try to cut or tear it off
+ if(body_part != UPPER_TORSO && body_part != LOWER_TORSO) //as hilarious as it is, getting hit on the chest too much shouldn't effectively gib you.
+ if(config.limbs_can_break && brute_dam >= max_damage * config.organ_health_multiplier)
+ if( (edge && prob(5 * brute)) || (brute > 20 && prob(2 * brute)) )
+ droplimb(1)
+ return
+
owner.updatehealth()
var/result = update_icon()
@@ -194,18 +194,6 @@
var/result = update_icon()
return result
-
-/datum/organ/external/proc/embed(var/obj/item/weapon/W, var/silent = 0)
- if(!silent)
- owner.visible_message("
")
- implants += W
- owner.embedded_flag = 1
- owner.verbs += /mob/proc/yank_out_object
- W.add_blood(owner)
- if(ismob(W.loc))
- var/mob/living/H = W.loc
- H.drop_item()
- W.loc = owner
/*
This function completely restores a damaged organ to perfect condition.
*/
@@ -218,6 +206,9 @@ This function completely restores a damaged organ to perfect condition.
perma_injury = 0
brute_dam = 0
burn_dam = 0
+ germ_level = 0
+ wounds.Cut()
+ number_wounds = 0
// handle internal organs
for(var/datum/organ/internal/current_organ in internal_organs)
@@ -235,11 +226,11 @@ This function completely restores a damaged organ to perfect condition.
/datum/organ/external/proc/createwound(var/type = CUT, var/damage)
if(damage == 0) return
- //moved this before the open_wound check so that having many small wounds for example doesn't somehow protect you from taking internal damage
+ //moved this before the open_wound check so that having many small wounds for example doesn't somehow protect you from taking internal damage (because of the return)
//Possibly trigger an internal wound, too.
var/local_damage = brute_dam + burn_dam + damage
if(damage > 15 && type != BURN && local_damage > 30 && prob(damage) && !(status & ORGAN_ROBOT))
- var/datum/wound/internal_bleeding/I = new (15)
+ var/datum/wound/internal_bleeding/I = new (min(damage - 15, 15))
wounds += I
owner.custom_pain("You feel something rip in your [display_name]!", 1)
@@ -277,25 +268,6 @@ This function completely restores a damaged organ to perfect condition.
if(W)
wounds += W
-/datum/organ/external/proc/get_wound_type(var/type = CUT, var/damage)
- //if you look a the names in the wound's stages list for each wound type you will see the logic behind these values
- switch(type)
- if(CUT)
- if (damage <= 5) return /datum/wound/cut/small
- if (damage <= 15) return /datum/wound/cut/deep
- if (damage <= 25) return /datum/wound/cut/flesh
- if (damage <= 50) return /datum/wound/cut/gaping
- if (damage <= 60) return /datum/wound/cut/gaping_big
- return /datum/wound/cut/massive
- if(BRUISE)
- return /datum/wound/bruise
- if(BURN)
- if (damage <= 5) return /datum/wound/burn/moderate
- if (damage <= 15) return /datum/wound/burn/large
- if (damage <= 30) return /datum/wound/burn/severe
- if (damage <= 40) return /datum/wound/burn/deep
- return /datum/wound/burn/carbonised
-
/****************************************************
PROCESSING & UPDATING
****************************************************/
@@ -303,6 +275,8 @@ This function completely restores a damaged organ to perfect condition.
//Determines if we even need to process this organ.
/datum/organ/external/proc/need_process()
+ if(destspawn) //Missing limb is missing
+ return 0
if(status && status != ORGAN_ROBOT) // If it's robotic, that's fine it will have a status.
return 1
if(brute_dam || burn_dam)
@@ -310,21 +284,13 @@ This function completely restores a damaged organ to perfect condition.
if(last_dam != brute_dam + burn_dam) // Process when we are fully healed up.
last_dam = brute_dam + burn_dam
return 1
- last_dam = brute_dam + burn_dam
+ else
+ last_dam = brute_dam + burn_dam
+ if(germ_level)
+ return 1
return 0
/datum/organ/external/process()
- // Process wounds, doing healing etc. Only do this every few ticks to save processing power
- if(owner.life_tick % wound_update_accuracy == 0)
- update_wounds()
-
- //Chem traces slowly vanish
- if(owner.life_tick % 10 == 0)
- for(var/chemID in trace_chemicals)
- trace_chemicals[chemID] = trace_chemicals[chemID] - 1
- if(trace_chemicals[chemID] <= 0)
- trace_chemicals.Remove(chemID)
-
//Dismemberment
if(status & ORGAN_DESTROYED)
if(!destspawn && config.limbs_can_break)
@@ -336,56 +302,134 @@ This function completely restores a damaged organ to perfect condition.
owner.update_body(1)
return
+ // Process wounds, doing healing etc. Only do this every few ticks to save processing power
+ if(owner.life_tick % wound_update_accuracy == 0)
+ update_wounds()
+
+ //Chem traces slowly vanish
+ if(owner.life_tick % 10 == 0)
+ for(var/chemID in trace_chemicals)
+ trace_chemicals[chemID] = trace_chemicals[chemID] - 1
+ if(trace_chemicals[chemID] <= 0)
+ trace_chemicals.Remove(chemID)
+
//Bone fracurtes
if(config.bones_can_break && brute_dam > min_broken_damage * config.organ_health_multiplier && !(status & ORGAN_ROBOT))
src.fracture()
if(!(status & ORGAN_BROKEN))
perma_injury = 0
+ //Infections
update_germs()
- return
//Updating germ levels. Handles organ germ levels and necrosis.
-#define GANGREN_LEVEL_ONE 100
-#define GANGREN_LEVEL_TWO 1000
-#define GANGREN_LEVEL_TERMINAL 2500
-#define GERM_TRANSFER_AMOUNT germ_level/500
+/*
+The INFECTION_LEVEL values defined in setup.dm control the time it takes to reach the different
+infection levels. Since infection growth is exponential, you can adjust the time it takes to get
+from one germ_level to another using the rough formula:
+
+desired_germ_level = initial_germ_level*e^(desired_time_in_seconds/1000)
+
+So if I wanted it to take an average of 15 minutes to get from level one (100) to level two
+I would set INFECTION_LEVEL_TWO to 100*e^(15*60/1000) = 245. Note that this is the average time,
+the actual time is dependent on RNG.
+
+INFECTION_LEVEL_ONE below this germ level nothing happens, and the infection doesn't grow
+INFECTION_LEVEL_TWO above this germ level the infection will start to spread to internal and adjacent organs
+INFECTION_LEVEL_THREE above this germ level the player will take additional toxin damage per second, and will die in minutes without
+ antitox. also, above this germ level you will need to overdose on spaceacillin to reduce the germ_level.
+
+Note that amputating the affected organ does in fact remove the infection from the player's body.
+*/
/datum/organ/external/proc/update_germs()
- if(status & ORGAN_ROBOT|ORGAN_DESTROYED) //Robotic limbs shouldn't be infected, nor should nonexistant limbs.
+ if(status & (ORGAN_ROBOT|ORGAN_DESTROYED) || (owner.species && owner.species.flags & IS_PLANT)) //Robotic limbs shouldn't be infected, nor should nonexistant limbs.
germ_level = 0
return
- if(germ_level > 0 && owner.bodytemperature >= 170) //cryo stops germs from moving and doing their bad stuffs
- //Syncing germ levels with external wounds
+ if(owner.bodytemperature >= 170) //cryo stops germs from moving and doing their bad stuffs
+ //** Syncing germ levels with external wounds
+ handle_germ_sync()
+
+ //** Handle antibiotics and curing infections
+ handle_antibiotics()
+
+ //** Handle the effects of infections
+ handle_germ_effects()
+
+/datum/organ/external/proc/handle_germ_sync()
+ var/antibiotics = owner.reagents.get_reagent_amount("spaceacillin")
+ for(var/datum/wound/W in wounds)
+ //Open wounds can become infected
+ if (owner.germ_level > W.germ_level && W.infection_check())
+ W.germ_level++
+
+ if (antibiotics < 5)
for(var/datum/wound/W in wounds)
- if(!W.bandaged && !W.salved)
- W.germ_level = max(W.germ_level, germ_level) //Wounds get all the germs
- if (W.germ_level > germ_level) //Badly infected wounds raise internal germ levels
- germ_level++
+ //Infected wounds raise the organ's germ level
+ if (W.germ_level > germ_level)
+ germ_level++
+ break //limit increase to a maximum of one per second
- if(germ_level > GANGREN_LEVEL_ONE && prob(round(germ_level/100)))
- germ_level++
- owner.adjustToxLoss(1)
+/datum/organ/external/proc/handle_germ_effects()
+ var/antibiotics = owner.reagents.get_reagent_amount("spaceacillin")
- if(germ_level > GANGREN_LEVEL_TWO)
- germ_level++
- owner.adjustToxLoss(1)
-/*
- if(germ_level > GANGREN_LEVEL_TERMINAL)
- if (!(status & ORGAN_DEAD))
- status |= ORGAN_DEAD
- owner << "
"
- owner.update_body(1)
- if (prob(10)) //Spreading the fun
- if (children) //To child organs
- for (var/datum/organ/external/child in children)
- if (!(child.status & (ORGAN_DEAD|ORGAN_DESTROYED|ORGAN_ROBOT)))
- child.germ_level += round(GERM_TRANSFER_AMOUNT)
- if (parent)
- if (!(parent.status & (ORGAN_DEAD|ORGAN_DESTROYED|ORGAN_ROBOT)))
- parent.germ_level += round(GERM_TRANSFER_AMOUNT)
-*/
+ if (germ_level > 0 && germ_level < INFECTION_LEVEL_ONE && prob(60)) //this could be an else clause, but it looks cleaner this way
+ germ_level-- //since germ_level increases at a rate of 1 per second with dirty wounds, prob(60) should give us about 5 minutes before level one.
+
+ if(germ_level >= INFECTION_LEVEL_ONE)
+ //having an infection raises your body temperature
+ var/fever_temperature = (owner.species.heat_level_1 - owner.species.body_temperature - 5)* min(germ_level/INFECTION_LEVEL_TWO, 1) + owner.species.body_temperature
+ //need to make sure we raise temperature fast enough to get around environmental cooling preventing us from reaching fever_temperature
+ owner.bodytemperature += between(0, (fever_temperature - T20C)/BODYTEMP_COLD_DIVISOR + 1, fever_temperature - owner.bodytemperature)
+
+ if(prob(round(germ_level/10)))
+ if (antibiotics < 5)
+ germ_level++
+
+ if (prob(10)) //adjust this to tweak how fast people take toxin damage from infections
+ owner.adjustToxLoss(1)
+
+ if(germ_level >= INFECTION_LEVEL_TWO && antibiotics < 5)
+ //spread the infection to internal organs
+ var/datum/organ/internal/target_organ = null //make internal organs become infected one at a time instead of all at once
+ for (var/datum/organ/internal/I in internal_organs)
+ if (I.germ_level > 0 && I.germ_level < min(germ_level, INFECTION_LEVEL_TWO)) //once the organ reaches whatever we can give it, or level two, switch to a different one
+ if (!target_organ || I.germ_level > target_organ.germ_level) //choose the organ with the highest germ_level
+ target_organ = I
+
+ if (!target_organ)
+ //figure out which organs we can spread germs to and pick one at random
+ var/list/candidate_organs = list()
+ for (var/datum/organ/internal/I in internal_organs)
+ if (I.germ_level < germ_level)
+ candidate_organs += I
+ if (candidate_organs.len)
+ target_organ = pick(candidate_organs)
+
+ if (target_organ)
+ target_organ.germ_level++
+
+ //spread the infection to child and parent organs
+ if (children)
+ for (var/datum/organ/external/child in children)
+ if (child.germ_level < germ_level && !(child.status & ORGAN_ROBOT))
+ if (child.germ_level < INFECTION_LEVEL_ONE*2 || prob(30))
+ child.germ_level++
+
+ if (parent)
+ if (parent.germ_level < germ_level && !(parent.status & ORGAN_ROBOT))
+ if (parent.germ_level < INFECTION_LEVEL_ONE*2 || prob(30))
+ parent.germ_level++
+
+ if(germ_level >= INFECTION_LEVEL_THREE && antibiotics < 30) //overdosing is necessary to stop severe infections
+ if (!(status & ORGAN_DEAD))
+ status |= ORGAN_DEAD
+ owner << "
"
+ owner.update_body(1)
+
+ germ_level++
+ owner.adjustToxLoss(1)
//Updating wounds. Handles wound natural I had some free spachealing, internal bleedings and infections
/datum/organ/external/proc/update_wounds()
@@ -401,16 +445,15 @@ This function completely restores a damaged organ to perfect condition.
// let the GC handle the deletion of the wound
// Internal wounds get worse over time. Low temperatures (cryo) stop them.
- if(W.internal && !W.is_treated() && owner.bodytemperature >= 170)
+ if(W.internal && owner.bodytemperature >= 170)
var/bicardose = owner.reagents.get_reagent_amount("bicaridine")
var/inaprovaline = owner.reagents.get_reagent_amount("inaprovaline")
- if(!bicardose || !inaprovaline) //bicaridine and inaprovaline stop internal wounds from growing bigger with time, and also stop bleeding
+ if(!(W.can_autoheal() || (bicardose && inaprovaline))) //bicaridine and inaprovaline stop internal wounds from growing bigger with time, unless it is so small that it is already healing
W.open_wound(0.1 * wound_update_accuracy)
- owner.vessel.remove_reagent("blood",0.05 * W.damage * wound_update_accuracy)
if(bicardose >= 30) //overdose of bicaridine begins healing IB
W.damage = max(0, W.damage - 0.2)
- owner.vessel.remove_reagent("blood",0.02 * W.damage * wound_update_accuracy)
+ owner.vessel.remove_reagent("blood", wound_update_accuracy * W.damage/40) //line should possibly be moved to handle_blood, so all the bleeding stuff is in one place.
if(prob(1 * wound_update_accuracy))
owner.custom_pain("You feel a stabbing pain in your [display_name]!",1)
@@ -418,7 +461,7 @@ This function completely restores a damaged organ to perfect condition.
var/heal_amt = 0
// if damage >= 50 AFTER treatment then it's probably too severe to heal within the timeframe of a round.
- if (W.is_treated() && W.wound_damage() < 50)
+ if (W.can_autoheal() && W.wound_damage() < 50)
heal_amt += 0.5
//we only update wounds once in [wound_update_accuracy] ticks so have to emulate realtime
@@ -433,8 +476,8 @@ This function completely restores a damaged organ to perfect condition.
// Salving also helps against infection
if(W.germ_level > 0 && W.salved && prob(2))
- W.germ_level = 0
W.disinfected = 1
+ W.germ_level = 0
// sync the organ's damage with its wounds
src.update_damages()
@@ -525,13 +568,27 @@ This function completely restores a damaged organ to perfect condition.
src.status &= ~ORGAN_BROKEN
src.status &= ~ORGAN_BLEEDING
src.status &= ~ORGAN_SPLINTED
+ src.status &= ~ORGAN_DEAD
for(var/implant in implants)
del(implant)
+ germ_level = 0
+
+ //Replace all wounds on that arm with one wound on parent organ.
+ wounds.Cut()
+ if (parent)
+ var/datum/wound/W
+ if(max_damage < 50)
+ W = new/datum/wound/lost_limb/small(max_damage)
+ else
+ W = new/datum/wound/lost_limb(max_damage)
+ parent.wounds += W
+ parent.update_damages()
+ update_damages()
+
// If any organs are attached to this, destroy them
- for(var/datum/organ/external/O in owner.organs)
- if(O.parent == src)
- O.droplimb(1)
+ for(var/datum/organ/external/O in children)
+ O.droplimb(1)
var/obj/organ //Dropped limb object
switch(body_part)
@@ -581,33 +638,37 @@ This function completely restores a damaged organ to perfect condition.
if(!(status & ORGAN_ROBOT))
organ = new /obj/item/weapon/organ/l_foot(owner.loc, owner)
owner.u_equip(owner.shoes)
+
+ destspawn = 1
+ //Robotic limbs explode if sabotaged.
+ if(status & ORGAN_ROBOT && !no_explode && sabotaged)
+ owner.visible_message("\red \The [owner]'s [display_name] explodes violently!",\
+ "\red
",\
+ "You hear an explosion followed by a scream!")
+ explosion(get_turf(owner),-1,-1,2,3)
+ var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread()
+ spark_system.set_up(5, 0, owner)
+ spark_system.attach(owner)
+ spark_system.start()
+ spawn(10)
+ del(spark_system)
+
+ owner.visible_message("\red [owner.name]'s [display_name] flies off in an arc.",\
+ "
",\
+ "You hear a terrible sound of ripping tendons and flesh.")
+
if(organ)
- destspawn = 1
- //Robotic limbs explode if sabotaged.
- if(status & ORGAN_ROBOT && !no_explode && sabotaged)
- owner.visible_message("\red \The [owner]'s [display_name] explodes violently!",\
- "\red
",\
- "You hear an explosion followed by a scream!")
- explosion(get_turf(owner),-1,-1,2,3)
- var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread()
- spark_system.set_up(5, 0, owner)
- spark_system.attach(owner)
- spark_system.start()
- spawn(10)
- del(spark_system)
-
- owner.visible_message("\red [owner.name]'s [display_name] flies off in an arc.",\
- "
",\
- "You hear a terrible sound of ripping tendons and flesh.")
-
//Throw organs around
var/lol = pick(cardinal)
step(organ,lol)
- owner.update_body(1)
+ owner.update_body(1)
- // OK so maybe your limb just flew off, but if it was attached to a pair of cuffs then hooray! Freedom!
- release_restraints()
+ // OK so maybe your limb just flew off, but if it was attached to a pair of cuffs then hooray! Freedom!
+ release_restraints()
+
+ if(vital)
+ owner.death()
/****************************************************
HELPERS
@@ -637,6 +698,15 @@ This function completely restores a damaged organ to perfect condition.
W.bandaged = 1
return rval
+/datum/organ/external/proc/disinfect()
+ var/rval = 0
+ for(var/datum/wound/W in wounds)
+ if(W.internal) continue
+ rval |= !W.disinfected
+ W.disinfected = 1
+ W.germ_level = 0
+ return rval
+
/datum/organ/external/proc/clamp()
var/rval = 0
src.status &= ~ORGAN_BLEEDING
@@ -654,8 +724,10 @@ This function completely restores a damaged organ to perfect condition.
return rval
/datum/organ/external/proc/fracture()
+
if(status & ORGAN_BROKEN)
return
+
owner.visible_message(\
"\red You hear a loud cracking sound coming from \the [owner].",\
"\red
",\
@@ -672,6 +744,25 @@ This function completely restores a damaged organ to perfect condition.
if (prob(25))
release_restraints()
+ // This is mostly for the ninja suit to stop ninja being so crippled by breaks.
+ // TODO: consider moving this to a suit proc or process() or something during
+ // hardsuit rewrite.
+ if(!(status & ORGAN_SPLINTED) && istype(owner,/mob/living/carbon/human))
+
+ var/mob/living/carbon/human/H = owner
+
+ if(H.wear_suit && istype(H.wear_suit,/obj/item/clothing/suit/space))
+
+ var/obj/item/clothing/suit/space/suit = H.wear_suit
+
+ if(isnull(suit.supporting_limbs))
+ return
+
+ owner << "You feel \the [suit] constrict about your [display_name], supporting it."
+ status |= ORGAN_SPLINTED
+ suit.supporting_limbs |= src
+ return
+
/datum/organ/external/proc/robotize()
src.status &= ~ORGAN_BROKEN
src.status &= ~ORGAN_BLEEDING
@@ -696,9 +787,9 @@ This function completely restores a damaged organ to perfect condition.
/datum/organ/external/proc/get_damage() //returns total damage
return max(brute_dam + burn_dam - perma_injury, perma_injury) //could use health?
-/datum/organ/external/proc/is_infected()
+/datum/organ/external/proc/has_infected_wound()
for(var/datum/wound/W in wounds)
- if(W.germ_level > 100)
+ if(W.germ_level > INFECTION_LEVEL_ONE)
return 1
return 0
@@ -715,6 +806,43 @@ This function completely restores a damaged organ to perfect condition.
/datum/organ/external/proc/is_usable()
return !(status & (ORGAN_DESTROYED|ORGAN_MUTATED|ORGAN_DEAD))
+/datum/organ/external/proc/is_broken()
+ return ((status & ORGAN_BROKEN) && !(status & ORGAN_SPLINTED))
+
+/datum/organ/external/proc/is_malfunctioning()
+ return ((status & ORGAN_ROBOT) && prob(brute_dam + burn_dam))
+
+//for arms and hands
+/datum/organ/external/proc/process_grasp(var/obj/item/c_hand, var/hand_name)
+ if (!c_hand)
+ return
+
+ if(is_broken())
+ owner.u_equip(c_hand)
+ var/emote_scream = pick("screams in pain and", "lets out a sharp cry and", "cries out and")
+ owner.emote("me", 1, "[(owner.species && owner.species.flags & NO_PAIN) ? "" : emote_scream ] drops what they were holding in their [hand_name]!")
+ if(is_malfunctioning())
+ owner.u_equip(c_hand)
+ owner.emote("me", 1, "drops what they were holding, their [hand_name] malfunctioning!")
+ var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread()
+ spark_system.set_up(5, 0, owner)
+ spark_system.attach(owner)
+ spark_system.start()
+ spawn(10)
+ del(spark_system)
+
+/datum/organ/external/proc/embed(var/obj/item/weapon/W, var/silent = 0)
+ if(!silent)
+ owner.visible_message("
")
+ implants += W
+ owner.embedded_flag = 1
+ owner.verbs += /mob/proc/yank_out_object
+ W.add_blood(owner)
+ if(ismob(W.loc))
+ var/mob/living/H = W.loc
+ H.drop_item()
+ W.loc = owner
+
/****************************************************
ORGAN DEFINES
****************************************************/
@@ -726,7 +854,7 @@ This function completely restores a damaged organ to perfect condition.
max_damage = 75
min_broken_damage = 40
body_part = UPPER_TORSO
-
+ vital = 1
/datum/organ/external/groin
name = "groin"
@@ -735,6 +863,7 @@ This function completely restores a damaged organ to perfect condition.
max_damage = 50
min_broken_damage = 30
body_part = LOWER_TORSO
+ vital = 1
/datum/organ/external/l_arm
name = "l_arm"
@@ -744,6 +873,10 @@ This function completely restores a damaged organ to perfect condition.
min_broken_damage = 20
body_part = ARM_LEFT
+ process()
+ ..()
+ process_grasp(owner.l_hand, "left hand")
+
/datum/organ/external/l_leg
name = "l_leg"
display_name = "left leg"
@@ -761,6 +894,10 @@ This function completely restores a damaged organ to perfect condition.
min_broken_damage = 20
body_part = ARM_RIGHT
+ process()
+ ..()
+ process_grasp(owner.r_hand, "right hand")
+
/datum/organ/external/r_leg
name = "r_leg"
display_name = "right leg"
@@ -796,6 +933,10 @@ This function completely restores a damaged organ to perfect condition.
min_broken_damage = 15
body_part = HAND_RIGHT
+ process()
+ ..()
+ process_grasp(owner.r_hand, "right hand")
+
/datum/organ/external/l_hand
name = "l_hand"
display_name = "left hand"
@@ -804,6 +945,10 @@ This function completely restores a damaged organ to perfect condition.
min_broken_damage = 15
body_part = HAND_LEFT
+ process()
+ ..()
+ process_grasp(owner.l_hand, "left hand")
+
/datum/organ/external/head
name = "head"
icon_name = "head"
@@ -812,6 +957,7 @@ This function completely restores a damaged organ to perfect condition.
min_broken_damage = 40
body_part = HEAD
var/disfigured = 0
+ vital = 1
/datum/organ/external/head/get_icon(var/icon/race_icon, var/icon/deform_icon)
if (!owner)
diff --git a/code/modules/organs/organ_internal.dm b/code/modules/organs/organ_internal.dm
index bb1ce19a..4ed10047 100644
--- a/code/modules/organs/organ_internal.dm
+++ b/code/modules/organs/organ_internal.dm
@@ -21,15 +21,47 @@
return damage >= min_broken_damage
+
/datum/organ/internal/New(mob/living/carbon/human/H)
..()
var/datum/organ/external/E = H.organs_by_name[src.parent_organ]
if(E.internal_organs == null)
E.internal_organs = list()
- E.internal_organs += src
- H.internal_organs[src.name] = src
+ E.internal_organs |= src
+ H.internal_organs |= src
src.owner = H
+/datum/organ/internal/process()
+ //Process infections
+
+ if (robotic >= 2 || (owner.species && owner.species.flags & IS_PLANT)) //TODO make robotic internal and external organs separate types of organ instead of a flag
+ germ_level = 0
+ return
+
+ if(owner.bodytemperature >= 170) //cryo stops germs from moving and doing their bad stuffs
+ //** Handle antibiotics and curing infections
+ handle_antibiotics()
+
+ //** Handle the effects of infections
+ var/antibiotics = owner.reagents.get_reagent_amount("spaceacillin")
+
+ if (germ_level > 0 && germ_level < INFECTION_LEVEL_ONE/2 && prob(30))
+ germ_level--
+
+ if (germ_level >= INFECTION_LEVEL_ONE/2)
+ //aiming for germ level to go from ambient to INFECTION_LEVEL_TWO in an average of 15 minutes
+ if(antibiotics < 5 && prob(round(germ_level/6)))
+ germ_level++
+
+ if (germ_level >= INFECTION_LEVEL_TWO)
+ var/datum/organ/external/parent = owner.get_organ(parent_organ)
+ //spread germs
+ if (antibiotics < 5 && parent.germ_level < germ_level && ( parent.germ_level < INFECTION_LEVEL_ONE*2 || prob(30) ))
+ parent.germ_level++
+
+ if (prob(3)) //about once every 30 seconds
+ take_damage(1,silent=prob(30))
+
/datum/organ/internal/proc/take_damage(amount, var/silent=0)
if(src.robotic == 2)
src.damage += (amount * 0.8)
@@ -40,7 +72,6 @@
if (!silent)
owner.custom_pain("Something inside your [parent.display_name] hurts a lot.", 1)
-
/datum/organ/internal/proc/emp_act(severity)
switch(robotic)
if(0)
@@ -90,13 +121,18 @@
parent_organ = "chest"
process()
+ ..()
+ if (germ_level > INFECTION_LEVEL_ONE)
+ if(prob(5))
+ owner.emote("cough") //respitory tract infection
+
if(is_bruised())
if(prob(2))
spawn owner.emote("me", 1, "coughs up blood!")
owner.drip(10)
if(prob(4))
spawn owner.emote("me", 1, "gasps for air!")
- owner.losebreath += 5
+ owner.losebreath += 15
/datum/organ/internal/liver
name = "liver"
@@ -104,6 +140,14 @@
var/process_accuracy = 10
process()
+ ..()
+ if (germ_level > INFECTION_LEVEL_ONE)
+ if(prob(1))
+ owner << "\red Your skin itches."
+ if (germ_level > INFECTION_LEVEL_TWO)
+ if(prob(1))
+ spawn owner.vomit()
+
if(owner.life_tick % process_accuracy == 0)
if(src.damage < 0)
src.damage = 0
@@ -115,9 +159,9 @@
src.damage += 0.2 * process_accuracy
//Damaged one shares the fun
else
- var/victim = pick(owner.internal_organs)
- var/datum/organ/internal/O = owner.internal_organs[victim]
- O.damage += 0.2 * process_accuracy
+ var/datum/organ/internal/O = pick(owner.internal_organs)
+ if(O)
+ O.damage += 0.2 * process_accuracy
//Detox can heal small amounts of damage
if (src.damage && src.damage < src.min_bruised_damage && owner.reagents.has_reagent("anti_toxin"))
@@ -146,6 +190,7 @@
parent_organ = "head"
process() //Eye damage replaces the old eye_stat var.
+ ..()
if(is_bruised())
owner.eye_blurry = 20
if(is_broken())
diff --git a/code/modules/organs/pain.dm b/code/modules/organs/pain.dm
index 250e9b98..49cc2073 100644
--- a/code/modules/organs/pain.dm
+++ b/code/modules/organs/pain.dm
@@ -102,8 +102,7 @@ mob/living/carbon/human/proc/handle_pain()
pain(damaged_organ.display_name, maxdam, 0)
// Damage to internal organs hurts a lot.
- for(var/organ_name in internal_organs)
- var/datum/organ/internal/I = internal_organs[organ_name]
+ for(var/datum/organ/internal/I in internal_organs)
if(I.damage > 2) if(prob(2))
var/datum/organ/external/parent = get_organ(I.parent_organ)
src.custom_pain("You feel a sharp pain in your [parent.display_name]", 1)
diff --git a/code/modules/organs/wound.dm b/code/modules/organs/wound.dm
index a7892486..9d4a5364 100644
--- a/code/modules/organs/wound.dm
+++ b/code/modules/organs/wound.dm
@@ -7,7 +7,7 @@
var/current_stage = 0
// description of the wound
- var/desc = ""
+ var/desc = "wound" //default in case something borks
// amount of damage this wound causes
var/damage = 0
@@ -31,7 +31,7 @@
var/germ_level = 0
/* These are defined by the wound type and should not be changed */
-
+
// stages such as "cut", "deep cut", etc.
var/list/stages
// internal wounds can only be fixed through surgery
@@ -51,7 +51,7 @@
// helper lists
var/tmp/list/desc_list = list()
var/tmp/list/damage_list = list()
-
+
New(var/damage)
created = world.time
@@ -65,7 +65,7 @@
src.damage = damage
max_bleeding_stage = src.desc_list.len - max_bleeding_stage
-
+
// initialize with the appropriate stage
src.init_stage(damage)
@@ -74,7 +74,7 @@
// returns 1 if there's a next stage, 0 otherwise
proc/init_stage(var/initial_damage)
current_stage = stages.len
-
+
while(src.current_stage > 1 && src.damage_list[current_stage-1] <= initial_damage / src.amount)
src.current_stage--
@@ -84,25 +84,26 @@
// the amount of damage per wound
proc/wound_damage()
return src.damage / src.amount
-
- // checks whether the wound has been appropriately treated
- // always returns 1 for wounds that are too minor to need treatment
- proc/is_treated()
+
+ proc/can_autoheal()
if(src.wound_damage() <= autoheal_cutoff)
return 1
+ return is_treated()
+
+ // checks whether the wound has been appropriately treated
+ proc/is_treated()
if(damage_type == BRUISE || damage_type == CUT)
return bandaged
else if(damage_type == BURN)
return salved
-
-
+
// Checks whether other other can be merged into src.
proc/can_merge(var/datum/wound/other)
if (other.type != src.type) return 0
if (other.current_stage != src.current_stage) return 0
if (other.damage_type != src.damage_type) return 0
- if (!(other.is_treated()) != !(src.is_treated())) return 0
+ if (!(other.can_autoheal()) != !(src.can_autoheal())) return 0
if (!(other.bandaged) != !(src.bandaged)) return 0
if (!(other.clamped) != !(src.clamped)) return 0
if (!(other.salved) != !(src.salved)) return 0
@@ -119,21 +120,29 @@
// checks if wound is considered open for external infections
// untreated cuts (and bleeding bruises) and burns are possibly infectable, chance higher if wound is bigger
- proc/can_infect()
- if (is_treated() && damage < 10)
+ proc/infection_check()
+ if (damage < 10) //small cuts, tiny bruises, and moderate burns shouldn't be infectable.
+ return 0
+ if (is_treated() && damage < 25) //anything less than a flesh wound (or equivalent) isn't infectable if treated properly
return 0
if (disinfected)
+ germ_level = 0 //reset this, just in case
return 0
+
+ if (damage_type == BRUISE && !bleeding()) //bruises only infectable if bleeding
+ return 0
+
var/dam_coef = round(damage/10)
switch (damage_type)
if (BRUISE)
- return prob(dam_coef*5) && bleeding() //bruises only infectable if bleeding
+ return prob(dam_coef*5)
if (BURN)
return prob(dam_coef*10)
if (CUT)
return prob(dam_coef*20)
return 0
+
// heal the given amount of damage, and if the given amount of damage was more
// than what needed to be healed, return how much heal was left
// set @heals_internal to also heal internal organ damage
@@ -170,23 +179,71 @@
proc/can_worsen(damage_type, damage)
if (src.damage_type != damage_type)
return 0 //incompatible damage types
-
+
if (src.amount > 1)
return 0
-
+
//with 1.5*, a shallow cut will be able to carry at most 30 damage,
//37.5 for a deep cut
//52.5 for a flesh wound, etc.
var/max_wound_damage = 1.5*src.damage_list[1]
if (src.damage + damage > max_wound_damage)
return 0
-
+
return 1
-
proc/bleeding()
- // internal wounds don't bleed in the sense of this function
- return ((wound_damage() > 30 || bleed_timer > 0) && !(bandaged||clamped) && (damage_type == BRUISE && wound_damage() >= 20 || damage_type == CUT && wound_damage() >= 5) && current_stage <= max_bleeding_stage && !src.internal)
+ if (src.internal)
+ return 0 // internal wounds don't bleed in the sense of this function
+
+ if (current_stage > max_bleeding_stage)
+ return 0
+
+ if (bandaged||clamped)
+ return 0
+
+ if (wound_damage() <= 30 && bleed_timer <= 0)
+ return 0 //Bleed timer has run out. Wounds with more than 30 damage don't stop bleeding on their own.
+
+ return (damage_type == BRUISE && wound_damage() >= 20 || damage_type == CUT && wound_damage() >= 5)
+
+/** WOUND DEFINITIONS **/
+
+//Note that the MINIMUM damage before a wound can be applied should correspond to
+//the damage amount for the stage with the same name as the wound.
+//e.g. /datum/wound/cut/deep should only be applied for 15 damage and up,
+//because in it's stages list, "deep cut" = 15.
+/proc/get_wound_type(var/type = CUT, var/damage)
+ switch(type)
+ if(CUT)
+ switch(damage)
+ if(70 to INFINITY)
+ return /datum/wound/cut/massive
+ if(60 to 70)
+ return /datum/wound/cut/gaping_big
+ if(50 to 60)
+ return /datum/wound/cut/gaping
+ if(25 to 50)
+ return /datum/wound/cut/flesh
+ if(15 to 25)
+ return /datum/wound/cut/deep
+ if(0 to 15)
+ return /datum/wound/cut/small
+ if(BRUISE)
+ return /datum/wound/bruise
+ if(BURN)
+ switch(damage)
+ if(50 to INFINITY)
+ return /datum/wound/burn/carbonised
+ if(40 to 50)
+ return /datum/wound/burn/deep
+ if(30 to 40)
+ return /datum/wound/burn/severe
+ if(15 to 30)
+ return /datum/wound/burn/large
+ if(0 to 15)
+ return /datum/wound/burn/moderate
+ return null //no wound
/** CUTS **/
/datum/wound/cut/small
@@ -201,7 +258,7 @@
damage_type = CUT
/datum/wound/cut/flesh
- max_bleeding_stage = 3
+ max_bleeding_stage = 4
stages = list("ugly ripped flesh wound" = 35, "ugly flesh wound" = 30, "flesh wound" = 25, "blood soaked clot" = 15, "large scab" = 5, "fresh skin" = 0)
damage_type = CUT
@@ -230,27 +287,40 @@ datum/wound/cut/massive
/** BURNS **/
/datum/wound/burn/moderate
- stages = list("ripped burn" = 10, "moderate burn" = 5, "moderate salved burn" = 2, "fresh skin" = 0)
+ stages = list("ripped burn" = 10, "moderate burn" = 5, "healing moderate burn" = 2, "fresh skin" = 0)
damage_type = BURN
/datum/wound/burn/large
- stages = list("ripped large burn" = 20, "large burn" = 15, "large salved burn" = 5, "fresh skin" = 0)
+ stages = list("ripped large burn" = 20, "large burn" = 15, "healing large burn" = 5, "fresh skin" = 0)
damage_type = BURN
/datum/wound/burn/severe
- stages = list("ripped severe burn" = 35, "severe burn" = 30, "severe salved burn" = 10, "burn scar" = 0)
+ stages = list("ripped severe burn" = 35, "severe burn" = 30, "healing severe burn" = 10, "burn scar" = 0)
damage_type = BURN
/datum/wound/burn/deep
- stages = list("ripped deep burn" = 45, "deep burn" = 40, "deep salved burn" = 15, "large burn scar" = 0)
+ stages = list("ripped deep burn" = 45, "deep burn" = 40, "healing deep burn" = 15, "large burn scar" = 0)
damage_type = BURN
/datum/wound/burn/carbonised
- stages = list("carbonised area" = 50, "treated carbonised area" = 20, "massive burn scar" = 0)
+ stages = list("carbonised area" = 50, "healing carbonised area" = 20, "massive burn scar" = 0)
damage_type = BURN
+/** INTERNAL BLEEDING **/
/datum/wound/internal_bleeding
internal = 1
- stages = list("severed vein" = 30, "cut vein" = 20, "damaged vein" = 10, "bruised vein" = 5)
+ stages = list("severed artery" = 30, "cut artery" = 20, "damaged artery" = 10, "bruised artery" = 5)
autoheal_cutoff = 5
max_bleeding_stage = 0 //all stages bleed. It's called internal bleeding after all.
+
+/** EXTERNAL ORGAN LOSS **/
+/datum/wound/lost_limb
+ damage_type = CUT
+ stages = list("ripped stump" = 65, "bloody stump" = 50, "clotted stump" = 25, "scarred stump" = 0)
+ max_bleeding_stage = 3
+
+ can_merge(var/datum/wound/other)
+ return 0 //cannot be merged
+
+/datum/wound/lost_limb/small
+ stages = list("ripped stump" = 40, "bloody stump" = 30, "clotted stump" = 15, "scarred stump" = 0)
diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm
index ea49067e..ca128c2c 100644
--- a/code/modules/paperwork/clipboard.dm
+++ b/code/modules/paperwork/clipboard.dm
@@ -44,6 +44,7 @@
return
/obj/item/weapon/clipboard/attackby(obj/item/weapon/W as obj, mob/user as mob)
+
if(istype(W, /obj/item/weapon/paper) || istype(W, /obj/item/weapon/photo))
user.drop_item()
W.loc = src
@@ -51,9 +52,11 @@
toppaper = W
user << "
"
update_icon()
- else if(toppaper)
- toppaper.attackby(usr.get_active_hand(), usr)
+
+ else if(istype(toppaper) && istype(W, /obj/item/weapon/pen))
+ toppaper.attackby(W, usr)
update_icon()
+
return
/obj/item/weapon/clipboard/attack_self(mob/user as mob)
@@ -85,32 +88,39 @@
if((usr.stat || usr.restrained()))
return
- if(usr.contents.Find(src))
+ if(src.loc == usr)
if(href_list["pen"])
- if(haspen)
+ if(istype(haspen) && (haspen.loc == src))
haspen.loc = usr.loc
usr.put_in_hands(haspen)
haspen = null
- if(href_list["addpen"])
+ else if(href_list["addpen"])
if(!haspen)
- if(istype(usr.get_active_hand(), /obj/item/weapon/pen))
- var/obj/item/weapon/pen/W = usr.get_active_hand()
+ var/obj/item/weapon/pen/W = usr.get_active_hand()
+ if(istype(W, /obj/item/weapon/pen))
usr.drop_item()
W.loc = src
haspen = W
usr << "
"
- if(href_list["write"])
- var/obj/item/P = locate(href_list["write"])
- if(P)
- if(usr.get_active_hand())
- P.attackby(usr.get_active_hand(), usr)
+ else if(href_list["write"])
+ var/obj/item/weapon/P = locate(href_list["write"])
+
+ if(P && (P.loc == src) && istype(P, /obj/item/weapon/paper) && (P == toppaper) )
+
+ var/obj/item/I = usr.get_active_hand()
+
+ if(istype(I, /obj/item/weapon/pen))
+
+ P.attackby(I, usr)
- if(href_list["remove"])
+ else if(href_list["remove"])
var/obj/item/P = locate(href_list["remove"])
- if(P)
+
+ if(P && (P.loc == src) && (istype(P, /obj/item/weapon/paper) || istype(P, /obj/item/weapon/photo)) )
+
P.loc = usr.loc
usr.put_in_hands(P)
if(P == toppaper)
@@ -121,9 +131,11 @@
else
toppaper = null
- if(href_list["read"])
+ else if(href_list["read"])
var/obj/item/weapon/paper/P = locate(href_list["read"])
- if(P)
+
+ if(P && (P.loc == src) && istype(P, /obj/item/weapon/paper) )
+
if(!(istype(usr, /mob/living/carbon/human) || istype(usr, /mob/dead/observer) || istype(usr, /mob/living/silicon)))
usr << browse("
[stars(P.info)][P.stamps]", "window=[P.name]")
onclose(usr, "[P.name]")
@@ -131,18 +143,18 @@
usr << browse("
[P.info][P.stamps]", "window=[P.name]")
onclose(usr, "[P.name]")
- if(href_list["look"])
+ else if(href_list["look"])
var/obj/item/weapon/photo/P = locate(href_list["look"])
- if(P)
+ if(P && (P.loc == src) && istype(P, /obj/item/weapon/photo) )
P.show(usr)
- if(href_list["top"])
+ else if(href_list["top"]) // currently unused
var/obj/item/P = locate(href_list["top"])
- if(P)
+ if(P && (P.loc == src) && istype(P, /obj/item/weapon/paper) )
toppaper = P
usr << "
"
//Update everything
attack_self(usr)
update_icon()
- return
\ No newline at end of file
+ return
diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm
index 1dd51443..5bb1dde1 100644
--- a/code/modules/paperwork/filingcabinet.dm
+++ b/code/modules/paperwork/filingcabinet.dm
@@ -57,9 +57,7 @@
user.set_machine(src)
var/dat = "
[dat]", "window=filingcabinet;size=350x300")
@@ -89,12 +87,13 @@
//var/retrieveindex = text2num(href_list["retrieve"])
var/obj/item/P = locate(href_list["retrieve"])//contents[retrieveindex]
- if(P && in_range(src, usr))
+ if(istype(P) && (P.loc == src) && src.Adjacent(usr))
usr.put_in_hands(P)
updateUsrDialog()
icon_state = "[initial(icon_state)]-open"
- sleep(5)
- icon_state = initial(icon_state)
+ spawn(0)
+ sleep(5)
+ icon_state = initial(icon_state)
/*
diff --git a/code/modules/paperwork/folders.dm b/code/modules/paperwork/folders.dm
index 0f1b612e..868fac30 100644
--- a/code/modules/paperwork/folders.dm
+++ b/code/modules/paperwork/folders.dm
@@ -59,34 +59,34 @@
if((usr.stat || usr.restrained()))
return
- if(usr.contents.Find(src))
+ if(src.loc == usr)
if(href_list["remove"])
var/obj/item/P = locate(href_list["remove"])
- if(P && P.loc == src)
+ if(P && (P.loc == src) && istype(P))
P.loc = usr.loc
usr.put_in_hands(P)
- if(href_list["read"])
+ else if(href_list["read"])
var/obj/item/weapon/paper/P = locate(href_list["read"])
- if(P)
+ if(P && (P.loc == src) && istype(P))
if(!(istype(usr, /mob/living/carbon/human) || istype(usr, /mob/dead/observer) || istype(usr, /mob/living/silicon)))
usr << browse("
[stars(P.info)][P.stamps]", "window=[P.name]")
onclose(usr, "[P.name]")
else
usr << browse("
[P.info][P.stamps]", "window=[P.name]")
onclose(usr, "[P.name]")
- if(href_list["look"])
+ else if(href_list["look"])
var/obj/item/weapon/photo/P = locate(href_list["look"])
- if(P)
+ if(P && (P.loc == src) && istype(P))
P.show(usr)
- if(href_list["browse"])
+ else if(href_list["browse"])
var/obj/item/weapon/paper_bundle/P = locate(href_list["browse"])
- if(P)
+ if(P && (P.loc == src) && istype(P))
P.attack_self(usr)
onclose(usr, "[P.name]")
//Update everything
attack_self(usr)
update_icon()
- return
\ No newline at end of file
+ return
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 41eb32e2..9cf7cd69 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -40,6 +40,12 @@
pixel_y = rand(-8, 8)
pixel_x = rand(-9, 9)
stamps = ""
+
+ if(info != initial(info))
+ info = html_encode(info)
+ info = replacetext(info, "\n", "
")
+ info = parsepencode(info)
+
spawn(2)
update_icon()
updateinfolinks()
@@ -96,8 +102,8 @@
/obj/item/weapon/paper/attack_ai(var/mob/living/silicon/ai/user as mob)
var/dist
- if(istype(user) && user.current) //is AI
- dist = get_dist(src, user.current)
+ if(istype(user) && user.camera) //is AI
+ dist = get_dist(src, user.camera)
else //cyborg or AI not seeing through a camera
dist = get_dist(src, user)
if(dist < 2)
@@ -114,6 +120,22 @@
"
")
M << examine()
+ else if(user.zone_sel.selecting == "mouth") // lipstick wiping
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ if(H == user)
+ user << "
"
+ H.lip_style = null
+ H.update_body()
+ else
+ user.visible_message("
")
+ if(do_after(user, 10) && do_after(H, 10, 5, 0)) //user needs to keep their active hand, H does not.
+ user.visible_message("
")
+ H.lip_style = null
+ H.update_body()
+
/obj/item/weapon/paper/proc/addtofield(var/id, var/text, var/links = 0)
var/locid = 0
var/laststart = 1
@@ -182,7 +204,7 @@
t = replacetext(t, "\[/u\]", "")
t = replacetext(t, "\[large\]", "
")
if(!iscrayon)
@@ -192,8 +214,15 @@
t = replacetext(t, "\[/small\]", "")
t = replacetext(t, "\[list\]", "
")
+ t = replacetext(t, "\[cell\]", "")
+ t = replacetext(t, "\[logo\]", " ")
- t = "[t]"
+ t = "[t]"
else // If it is a crayon, and he still tries to use these, make them empty!
t = replacetext(t, "\[*\]", "")
t = replacetext(t, "\[hr\]", "")
@@ -201,8 +230,13 @@
t = replacetext(t, "\[/small\]", "")
t = replacetext(t, "\[list\]", "")
t = replacetext(t, "\[/list\]", "")
+ t = replacetext(t, "\[table\]", "")
+ t = replacetext(t, "\[/table\]", "")
+ t = replacetext(t, "\[row\]", "")
+ t = replacetext(t, "\[cell\]", "")
+ t = replacetext(t, "\[logo\]", "")
- t = "[t]"
+ t = "[t]"
// t = replacetext(t, "#", "") // Junk converted to nothing!
@@ -282,9 +316,10 @@
iscrayon = 1
- if((!in_range(src, usr) && loc != usr && !( istype(loc, /obj/item/weapon/clipboard) ) && loc.loc != usr && usr.get_active_hand() != i)) // Some check to see if he's allowed to write
+ // if paper is not in usr, then it must be near them, or in a clipboard or folder, which must be in or near usr
+ if(src.loc != usr && !src.Adjacent(usr) && !((istype(src.loc, /obj/item/weapon/clipboard) || istype(src.loc, /obj/item/weapon/folder)) && (src.loc.loc == usr || src.loc.Adjacent(usr)) ) )
return
-
+/*
t = checkhtml(t)
// check for exploits
@@ -294,7 +329,8 @@
log_admin("PAPER: [usr] ([usr.ckey]) tried to use forbidden word in [src]: [bad].")
message_admins("PAPER: [usr] ([usr.ckey]) tried to use forbidden word in [src]: [bad].")
return
-
+*/
+ t = html_encode(t)
t = replacetext(t, "\n", " ")
t = parsepencode(t, i, usr, iscrayon) // Encode everything from pencode to html
@@ -461,4 +497,4 @@
return
/obj/item/weapon/paper/crumpled/bloody
- icon_state = "scrap_bloodied"
\ No newline at end of file
+ icon_state = "scrap_bloodied"
diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm
index a1ded78e..03c59d4a 100644
--- a/code/modules/paperwork/paperbin.dm
+++ b/code/modules/paperwork/paperbin.dm
@@ -8,6 +8,7 @@
throw_speed = 3
throw_range = 7
pressure_resistance = 10
+ layer = OBJ_LAYER - 0.1
var/amount = 30 //How much paper is in the bin.
var/list/papers = new/list() //List of papers put in the bin for reference.
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 515de827..5300f2ed 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -21,7 +21,7 @@
w_class = 1.0
throw_speed = 7
throw_range = 15
- m_amt = 10
+ matter = list("metal" = 10)
var/colour = "black" //what colour the ink is!
pressure_resistance = 2
diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm
index 7f006325..6963fe2f 100644
--- a/code/modules/paperwork/photocopier.dm
+++ b/code/modules/paperwork/photocopier.dm
@@ -34,6 +34,8 @@
dat += "+
"
else if(toner)
dat += "Please insert paper to copy.
"
+ if(istype(user,/mob/living/silicon))
+ dat += "Print photo from database
"
dat += "Current toner level: [toner]"
if(!toner)
dat +=" Please insert a new toner cartridge!"
@@ -112,6 +114,27 @@
if(copies < maxcopies)
copies++
updateUsrDialog()
+ else if(href_list["aipic"])
+ if(!istype(usr,/mob/living/silicon)) return
+ if(toner >= 5)
+ var/mob/living/silicon/tempAI = usr
+ var/obj/item/device/camera/siliconcam/camera = tempAI.aiCamera
+
+ if(!camera)
+ return
+ var/datum/picture/selection = camera.selectpicture()
+ if (!selection)
+ return
+
+ var/obj/item/weapon/photo/p = new /obj/item/weapon/photo (src.loc)
+ p.construct(selection)
+ if (p.desc == "")
+ p.desc += "Copied by [tempAI.name]"
+ else
+ p.desc += " - Copied by [tempAI.name]"
+ toner -= 5
+ sleep(15)
+ updateUsrDialog()
attackby(obj/item/O as obj, mob/user as mob)
if(istype(O, /obj/item/weapon/paper))
diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm
index b6c25239..cef4a337 100644
--- a/code/modules/paperwork/photography.dm
+++ b/code/modules/paperwork/photography.dm
@@ -53,10 +53,10 @@
/obj/item/weapon/photo/proc/show(mob/user as mob)
user << browse_rsc(img, "tmp_photo.png")
user << browse("[name]" \
- + "" \
- + "  Written on the back: [scribble]" : ]"\
- + "", "window=book;size=200x[scribble ? 400 : 200]")
+ + "" \
+ + "  " \
+ + "[scribble ? " Written on the back: [scribble]" : ""]"\
+ + "", "window=book;size=192x[scribble ? 400 : 192]")
onclose(user, "[name]")
return
@@ -119,7 +119,7 @@
w_class = 2.0
flags = FPRINT | CONDUCT | TABLEPASS
slot_flags = SLOT_BELT
- m_amt = 2000
+ matter = list("metal" = 2000)
var/pictures_max = 10
var/pictures_left = 10
var/on = 1
@@ -152,41 +152,51 @@
..()
-/obj/item/device/camera/proc/get_icon(turf/the_turf as turf)
+/obj/item/device/camera/proc/get_icon(list/turfs, turf/center)
+
//Bigger icon base to capture those icons that were shifted to the next tile
//i.e. pretty much all wall-mounted machinery
var/icon/res = icon('icons/effects/96x96.dmi', "")
-
- var/icon/turficon = build_composite_icon(the_turf)
- res.Blend(turficon, ICON_OVERLAY, 33, 33)
+ // Initialize the photograph to black.
+ res.Blend("#000", ICON_OVERLAY)
var/atoms[] = list()
- for(var/atom/A in the_turf)
- if(A.invisibility) continue
- atoms.Add(A)
+ for(var/turf/the_turf in turfs)
+ // Add outselves to the list of stuff to draw
+ atoms.Add(the_turf);
+ // As well as anything that isn't invisible.
+ for(var/atom/A in the_turf)
+ if(A.invisibility) continue
+ atoms.Add(A)
- //Sorting icons based on levels
- var/gap = atoms.len
- var/swapped = 1
- while (gap > 1 || swapped)
- swapped = 0
- if(gap > 1)
- gap = round(gap / 1.247330950103979)
- if(gap < 1)
- gap = 1
- for(var/i = 1; gap + i <= atoms.len; i++)
- var/atom/l = atoms[i] //Fucking hate
- var/atom/r = atoms[gap+i] //how lists work here
- if(l.layer > r.layer) //no "atoms[i].layer" for me
- atoms.Swap(i, gap + i)
- swapped = 1
+ // Sort the atoms into their layers
+ var/list/sorted = sort_atoms_by_layer(atoms)
- for(var/i; i <= atoms.len; i++)
- var/atom/A = atoms[i]
+ for(var/i; i <= sorted.len; i++)
+ var/atom/A = sorted[i]
if(A)
- var/icon/img = getFlatIcon(A, A.dir)//build_composite_icon(A)
+ var/icon/img = getFlatIcon(A)//build_composite_icon(A)
+
+ // If what we got back is actually a picture, draw it.
if(istype(img, /icon))
- res.Blend(new/icon(img, "", A.dir), ICON_OVERLAY, 33 + A.pixel_x, 33 + A.pixel_y)
+ // Check if we're looking at a mob that's lying down
+ if(istype(A, /mob/living) && A:lying)
+ // If they are, apply that effect to their picture.
+ img.BecomeLying()
+ // Calculate where we are relative to the center of the photo
+ var/xoff = (A.x - center.x) * 32
+ var/yoff = (A.y - center.y) * 32
+ if (istype(A,/atom/movable))
+ xoff+=A:step_x
+ yoff+=A:step_y
+ res.Blend(img, blendMode2iconMode(A.blend_mode), 33 + A.pixel_x + xoff, 33 + A.pixel_y + yoff)
+
+ // Lastly, render any contained effects on top.
+ for(var/turf/the_turf in turfs)
+ // Calculate where we are relative to the center of the photo
+ var/xoff = (the_turf.x - center.x) * 32
+ var/yoff = (the_turf.y - center.y) * 32
+ res.Blend(getFlatIcon(the_turf.loc), blendMode2iconMode(the_turf.blend_mode),33 + xoff,33 + yoff)
return res
@@ -209,53 +219,10 @@
mob_detail += "You can also see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]."
return mob_detail
-
/obj/item/device/camera/afterattack(atom/target as mob|obj|turf|area, mob/user as mob, flag)
if(!on || !pictures_left || ismob(target.loc)) return
+ captureimage(target, user, flag)
- var/x_c = target.x - 1
- var/y_c = target.y + 1
- var/z_c = target.z
-
- var/icon/temp = icon('icons/effects/96x96.dmi',"")
- var/icon/black = icon('icons/turf/space.dmi', "black")
- var/mobs = ""
- for(var/i = 1; i <= 3; i++)
- for(var/j = 1; j <= 3; j++)
- var/turf/T = locate(x_c, y_c, z_c)
- var/mob/dummy = new(T) //Go go visibility check dummy
- var/viewer = user
- if(user.client) //To make shooting through security cameras possible
- viewer = user.client.eye
- if(dummy in viewers(world.view, viewer))
- temp.Blend(get_icon(T), ICON_OVERLAY, 32 * (j-1-1), 32 - 32 * (i-1))
- else
- temp.Blend(black, ICON_OVERLAY, 32 * (j-1), 64 - 32 * (i-1))
- mobs += get_mobs(T)
- dummy.loc = null
- dummy = null //Alas, nameless creature //garbage collect it instead
- x_c++
- y_c--
- x_c = x_c - 3
-
- var/obj/item/weapon/photo/P = new/obj/item/weapon/photo()
- P.loc = user.loc
- if(!user.get_inactive_hand())
- user.put_in_inactive_hand(P)
- var/icon/small_img = icon(temp)
- var/icon/tiny_img = icon(temp)
- var/icon/ic = icon('icons/obj/items.dmi',"photo")
- var/icon/pc = icon('icons/obj/bureaucracy.dmi', "photo")
- small_img.Scale(8, 8)
- tiny_img.Scale(4, 4)
- ic.Blend(small_img,ICON_OVERLAY, 10, 13)
- pc.Blend(tiny_img,ICON_OVERLAY, 12, 19)
- P.icon = ic
- P.tiny = pc
- P.img = temp
- P.desc = mobs
- P.pixel_x = rand(-10, 10)
- P.pixel_y = rand(-10, 10)
playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, 1, -3)
pictures_left--
@@ -265,4 +232,74 @@
on = 0
spawn(64)
icon_state = icon_on
- on = 1
\ No newline at end of file
+ on = 1
+
+/obj/item/device/camera/proc/can_capture_turf(turf/T, mob/user)
+ var/mob/dummy = new(T) //Go go visibility check dummy
+ var/viewer = user
+ if(user.client) //To make shooting through security cameras possible
+ viewer = user.client.eye
+ var/can_see = (dummy in viewers(world.view, viewer)) != null
+
+ dummy.loc = null
+ dummy = null //Alas, nameless creature //garbage collect it instead
+ return can_see
+
+/obj/item/device/camera/proc/captureimage(atom/target, mob/user, flag)
+ var/x_c = target.x - 1
+ var/y_c = target.y + 1
+ var/z_c = target.z
+
+
+ var/list/turfs = list()
+ var/mobs = ""
+ for(var/i = 1; i <= 3; i++)
+ for(var/j = 1; j <= 3; j++)
+ var/turf/T = locate(x_c, y_c, z_c)
+ if(can_capture_turf(T, user))
+ turfs.Add(T)
+ mobs += get_mobs(T)
+ x_c++
+ y_c--
+ x_c = x_c - 3
+
+ var/datum/picture/P = createpicture(target, user, turfs, mobs, flag)
+ printpicture(user, P)
+
+/obj/item/device/camera/proc/createpicture(atom/target, mob/user, list/turfs, mobs, flag)
+ var/icon/photoimage = get_icon(turfs, target)
+
+ var/icon/small_img = icon(photoimage)
+ var/icon/tiny_img = icon(photoimage)
+ var/icon/ic = icon('icons/obj/items.dmi',"photo")
+ var/icon/pc = icon('icons/obj/bureaucracy.dmi', "photo")
+ small_img.Scale(8, 8)
+ tiny_img.Scale(4, 4)
+ ic.Blend(small_img,ICON_OVERLAY, 10, 13)
+ pc.Blend(tiny_img,ICON_OVERLAY, 12, 19)
+
+ var/datum/picture/P = new()
+ P.fields["author"] = user
+ P.fields["icon"] = ic
+ P.fields["tiny"] = pc
+ P.fields["img"] = photoimage
+ P.fields["desc"] = mobs
+ P.fields["pixel_x"] = rand(-10, 10)
+ P.fields["pixel_y"] = rand(-10, 10)
+
+ return P
+
+/obj/item/device/camera/proc/printpicture(mob/user, var/datum/picture/P)
+ var/obj/item/weapon/photo/Photo = new/obj/item/weapon/photo()
+ Photo.loc = user.loc
+ if(!user.get_inactive_hand())
+ user.put_in_inactive_hand(Photo)
+ Photo.construct(P)
+
+/obj/item/weapon/photo/proc/construct(var/datum/picture/P)
+ icon = P.fields["icon"]
+ tiny = P.fields["tiny"]
+ img = P.fields["img"]
+ desc = P.fields["desc"]
+ pixel_x = P.fields["pixel_x"]
+ pixel_y = P.fields["pixel_y"]
diff --git a/code/modules/paperwork/silicon_photography.dm b/code/modules/paperwork/silicon_photography.dm
new file mode 100644
index 00000000..4fbe953d
--- /dev/null
+++ b/code/modules/paperwork/silicon_photography.dm
@@ -0,0 +1,162 @@
+/**************
+* AI-specific *
+**************/
+/datum/picture
+ var/name = "image"
+ var/list/fields = list()
+
+/obj/item/device/camera/siliconcam
+ var/in_camera_mode = 0
+ var/photos_taken = 0
+ var/list/aipictures = list()
+
+/obj/item/device/camera/siliconcam/ai_camera //camera AI can take pictures with
+ name = "AI photo camera"
+
+/obj/item/device/camera/siliconcam/robot_camera //camera cyborgs can take pictures with
+ name = "Cyborg photo camera"
+
+/obj/item/device/camera/siliconcam/drone_camera //currently doesn't offer the verbs, thus cannot be used
+ name = "Drone photo camera"
+
+/obj/item/device/camera/siliconcam/proc/injectaialbum(var/datum/picture/P, var/sufix = "") //stores image information to a list similar to that of the datacore
+ photos_taken++
+ P.fields["name"] = "Image [photos_taken][sufix]"
+ aipictures += P
+
+/obj/item/device/camera/siliconcam/proc/injectmasteralbum(var/datum/picture/P) //stores image information to a list similar to that of the datacore
+ var/mob/living/silicon/robot/C = src.loc
+ if(C.connected_ai)
+ var/mob/A = P.fields["author"]
+ C.connected_ai.aiCamera.injectaialbum(P, " (taken by [A.name])")
+ C.connected_ai << " Image recorded and saved by [name]"
+ usr << " Image recorded and saved to remote database" //feedback to the Cyborg player that the picture was taken
+ else
+ injectaialbum(P)
+ usr << " Image recorded"
+
+/obj/item/device/camera/siliconcam/proc/selectpicture(obj/item/device/camera/siliconcam/cam)
+ if(!cam)
+ cam = getsource()
+
+ var/list/nametemp = list()
+ var/find
+ if(cam.aipictures.len == 0)
+ usr << " No images saved"
+ return
+ for(var/datum/picture/t in cam.aipictures)
+ nametemp += t.fields["name"]
+ find = input("Select image (numbered in order taken)") in nametemp
+
+ for(var/datum/picture/q in cam.aipictures)
+ if(q.fields["name"] == find)
+ return q
+
+/obj/item/device/camera/siliconcam/proc/viewpictures()
+ var/datum/picture/selection = selectpicture()
+
+ if(!selection)
+ return
+
+ var/obj/item/weapon/photo/P = new/obj/item/weapon/photo()
+ P.construct(selection)
+ P.show(usr)
+ usr << P.desc
+
+ // TG uses a special garbage collector.. qdel(P)
+ del(P) //so 10 thousand pictures items are not left in memory should an AI take them and then view them all.
+
+/obj/item/device/camera/siliconcam/proc/deletepicture(obj/item/device/camera/siliconcam/cam)
+ var/datum/picture/selection = selectpicture(cam)
+
+ if(!selection)
+ return
+
+ cam.aipictures -= selection
+ usr << " Image deleted"
+
+/obj/item/device/camera/siliconcam/ai_camera/can_capture_turf(turf/T, mob/user)
+ var/mob/living/silicon/ai = user
+ return ai.TurfAdjacent(T)
+
+/obj/item/device/camera/siliconcam/proc/toggle_camera_mode()
+ if(in_camera_mode)
+ camera_mode_off()
+ else
+ camera_mode_on()
+
+/obj/item/device/camera/siliconcam/proc/camera_mode_off()
+ src.in_camera_mode = 0
+ usr << " Camera Mode deactivated"
+
+/obj/item/device/camera/siliconcam/proc/camera_mode_on()
+ src.in_camera_mode = 1
+ usr << " Camera Mode activated"
+
+/obj/item/device/camera/siliconcam/ai_camera/printpicture(mob/user, datum/picture/P)
+ injectaialbum(P)
+ usr << " Image recorded"
+
+/obj/item/device/camera/siliconcam/robot_camera/printpicture(mob/user, datum/picture/P)
+ injectmasteralbum(P)
+
+/obj/item/device/camera/siliconcam/ai_camera/verb/take_image()
+ set category = "AI Commands"
+ set name = "Take Image"
+ set desc = "Takes an image"
+ set src in usr
+
+ toggle_camera_mode()
+
+/obj/item/device/camera/siliconcam/ai_camera/verb/view_images()
+ set category = "AI Commands"
+ set name = "View Images"
+ set desc = "View images"
+ set src in usr
+
+ viewpictures()
+
+/obj/item/device/camera/siliconcam/ai_camera/verb/delete_images()
+ set category = "AI Commands"
+ set name = "Delete Image"
+ set desc = "Delete image"
+ set src in usr
+
+ deletepicture()
+
+/obj/item/device/camera/siliconcam/robot_camera/verb/take_image()
+ set category ="Robot Commands"
+ set name = "Take Image"
+ set desc = "Takes an image"
+ set src in usr
+
+ toggle_camera_mode()
+
+/obj/item/device/camera/siliconcam/robot_camera/verb/view_images()
+ set category ="Robot Commands"
+ set name = "View Images"
+ set desc = "View images"
+ set src in usr
+
+ viewpictures()
+
+/obj/item/device/camera/siliconcam/robot_camera/verb/delete_images()
+ set category = "Robot Commands"
+ set name = "Delete Image"
+ set desc = "Delete a local image"
+ set src in usr
+
+ // Explicitly only allow deletion from the local camera
+ deletepicture(src)
+
+obj/item/device/camera/siliconcam/proc/getsource()
+ if(istype(src.loc, /mob/living/silicon/ai))
+ return src
+
+ var/mob/living/silicon/robot/C = src.loc
+ var/obj/item/device/camera/siliconcam/Cinfo
+ if(C.connected_ai)
+ Cinfo = C.connected_ai.aiCamera
+ else
+ Cinfo = src
+ return Cinfo
diff --git a/code/modules/paperwork/stamps.dm b/code/modules/paperwork/stamps.dm
index 9f0378b5..5374d98b 100644
--- a/code/modules/paperwork/stamps.dm
+++ b/code/modules/paperwork/stamps.dm
@@ -9,7 +9,7 @@
w_class = 1.0
throw_speed = 7
throw_range = 15
- m_amt = 60
+ matter = list("metal" = 60)
item_color = "cargo"
pressure_resistance = 2
attack_verb = list("stamped")
diff --git a/code/modules/power/antimatter/shielding.dm b/code/modules/power/antimatter/shielding.dm
index 9aa8f85c..4e17b6ca 100644
--- a/code/modules/power/antimatter/shielding.dm
+++ b/code/modules/power/antimatter/shielding.dm
@@ -210,8 +210,7 @@ proc/cardinalrange(var/center)
throwforce = 5
throw_speed = 1
throw_range = 2
- m_amt = 100
- w_amt = 2000
+ matter = list("metal" = 100,"waste" = 2000)
/obj/item/device/am_shielding_container/attackby(var/obj/item/I, var/mob/user)
if(istype(I, /obj/item/device/multitool) && istype(src.loc,/turf))
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 42a0db50..33d1d4e4 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -42,6 +42,14 @@
//NOTE: STUFF STOLEN FROM AIRLOCK.DM thx
+/obj/machinery/power/apc/high
+ cell_type = /obj/item/weapon/cell/high
+
+/obj/machinery/power/apc/super
+ cell_type = /obj/item/weapon/cell/super
+
+/obj/machinery/power/apc/hyper
+ cell_type = /obj/item/weapon/cell/hyper
/obj/machinery/power/apc
name = "area power controller"
@@ -53,7 +61,7 @@
var/areastring = null
var/obj/item/weapon/cell/cell
var/start_charge = 90 // initial cell charge %
- var/cell_type = 5000 // 0=no cell, 1=regular, 2=high-cap (x5) <- old, now it's just 0=no cell, otherwise dictate cellcapacity by changing this value. 1 used to be 1000, 2 was 2500
+ var/cell_type = /obj/item/weapon/cell/apc // 0=no cell, 1=regular, 2=high-cap (x5) <- old, now it's just 0=no cell, otherwise dictate cellcapacity by changing this value. 1 used to be 1000, 2 was 2500
var/opened = 0 //0=closed, 1=opened, 2=cover removed
var/shorted = 0
var/lighting = 3
@@ -166,8 +174,7 @@
has_electronics = 2 //installed and secured
// is starting with a power cell installed, create it and set its charge level
if(cell_type)
- src.cell = new/obj/item/weapon/cell(src)
- cell.maxcharge = cell_type // cell_type is maximum charge (old default was 1000 or 2500 (values one and two respectively)
+ src.cell = new cell_type(src)
cell.charge = start_charge * cell.maxcharge / 100.0 // (convert percentage to actual value)
var/area/A = src.loc.loc
@@ -507,7 +514,7 @@
user << "You start adding cables to the APC frame..."
playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1)
if(do_after(user, 20) && C.amount >= 10)
- var/turf/T = get_turf_loc(src)
+ var/turf/T = get_turf(src)
var/obj/structure/cable/N = T.get_cable_node()
if (prob(50) && electrocute_mob(usr, N, N))
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
@@ -673,7 +680,7 @@
return
// do APC interaction
- user.set_machine(src)
+ //user.set_machine(src)
src.interact(user)
/obj/machinery/power/apc/attack_alien(mob/living/carbon/alien/humanoid/user)
@@ -741,7 +748,7 @@
else
return 0 // 0 = User is not a Malf AI
-/obj/machinery/power/apc/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null)
+/obj/machinery/power/apc/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
if(!user)
return
@@ -752,7 +759,7 @@
"powerCellStatus" = cell ? cell.percent() : null,
"chargeMode" = chargemode,
"chargingStatus" = charging,
- "totalLoad" = lastused_equip + lastused_light + lastused_environ,
+ "totalLoad" = round(lastused_equip + lastused_light + lastused_environ),
"coverLocked" = coverlocked,
"siliconUser" = istype(user, /mob/living/silicon),
"malfStatus" = get_malf_status(user),
@@ -760,7 +767,7 @@
"powerChannels" = list(
list(
"title" = "Equipment",
- "powerLoad" = lastused_equip,
+ "powerLoad" = round(lastused_equip),
"status" = equipment,
"topicParams" = list(
"auto" = list("eqp" = 3),
@@ -770,7 +777,7 @@
),
list(
"title" = "Lighting",
- "powerLoad" = lastused_light,
+ "powerLoad" = round(lastused_light),
"status" = lighting,
"topicParams" = list(
"auto" = list("lgt" = 3),
@@ -780,7 +787,7 @@
),
list(
"title" = "Environment",
- "powerLoad" = lastused_environ,
+ "powerLoad" = round(lastused_environ),
"status" = environ,
"topicParams" = list(
"auto" = list("env" = 3),
@@ -792,7 +799,7 @@
)
// update the ui if it exists, returns null if no ui is passed/found
- ui = nanomanager.try_update_ui(user, src, ui_key, ui, data)
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
// the ui does not exist, so we'll create a new() one
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
@@ -1142,9 +1149,20 @@
else
return 0
+/obj/machinery/power/apc/proc/last_surplus()
+ if(terminal && terminal.powernet)
+ return terminal.powernet.last_surplus()
+ else
+ return 0
+
+//Returns 1 if the APC should attempt to charge
+/obj/machinery/power/apc/proc/attempt_charging()
+ return (chargemode && charging == 1 && operating)
+
/obj/machinery/power/apc/add_load(var/amount)
if(terminal && terminal.powernet)
- terminal.powernet.newload += amount
+ return terminal.powernet.draw_power(amount)
+ return 0
/obj/machinery/power/apc/avail()
if(terminal)
@@ -1163,9 +1181,6 @@
lastused_light = area.usage(LIGHT)
lastused_equip = area.usage(EQUIP)
lastused_environ = area.usage(ENVIRON)
- if(area.powerupdate)
- if(debug) log_debug("power update in [area.name] / [name]")
- area.clear_usage()
lastused_total = lastused_light + lastused_equip + lastused_environ
@@ -1176,13 +1191,7 @@
var/last_ch = charging
var/excess = surplus()
-
- if(!src.avail())
- main_status = 0
- else if(excess < 0)
- main_status = 1
- else
- main_status = 2
+ var/power_excess = 0
var/perapc = 0
if(terminal && terminal.powernet)
@@ -1191,31 +1200,36 @@
if(debug)
log_debug( "Status: [main_status] - Excess: [excess] - Last Equip: [lastused_equip] - Last Light: [lastused_light]")
+ if(area.powerupdate)
+ log_debug("power update in [area.name] / [name]")
+
if(cell && !shorted)
//var/cell_charge = cell.charge
var/cell_maxcharge = cell.maxcharge
- // draw power from cell as before
+ // Calculate how much power the APC will try to get from the grid.
+ var/target_draw = lastused_total
+ if (src.attempt_charging())
+ target_draw += min((cell_maxcharge - cell.charge), (cell_maxcharge*CHARGELEVEL))/CELLRATE
+ target_draw = min(target_draw, perapc) //limit power draw by perapc
- var/cellused = min(cell.charge, CELLRATE * lastused_total) // clamp deduction to a max, amount left in cell
- cell.use(cellused)
+ // try to draw power from the grid
+ var/power_drawn = 0
+ if (src.avail())
+ power_drawn = add_load(target_draw) //get some power from the powernet
- if(excess > 0 || perapc > lastused_total) // if power excess, or enough anyway, recharge the cell
- // by the same amount just used
- cell.give(cellused)
- add_load(cellused/CELLRATE) // add the load used to recharge the cell
+ //figure out how much power is left over after meeting demand
+ power_excess = power_drawn - lastused_total
+ if (power_excess < 0) //couldn't get enough power from the grid, we will need to take from the power cell.
- else // no excess, and not enough per-apc
+ charging = 0
- if( (cell.charge/CELLRATE+perapc) >= lastused_total) // can we draw enough from cell+grid to cover last usage?
-
- cell.give(CELLRATE * perapc) //recharge with what we can
- add_load(perapc) // so draw what we can from the grid
- charging = 0
+ var/required_power = -power_excess
+ if(cell.charge >= required_power*CELLRATE) // can we draw enough from cell to cover what's left over?
+ cell.use(required_power*CELLRATE)
else if (autoflag != 0) // not enough power available to run the last tick!
- charging = 0
chargecount = 0
// This turns everything off in the case that there is still a charge left on the battery, just not enough to run the room.
equipment = autoset(equipment, 0)
@@ -1223,8 +1237,15 @@
environ = autoset(environ, 0)
autoflag = 0
+ //Set external power status
+ if (!power_drawn)
+ main_status = 0
+ else if (power_excess < 0)
+ main_status = 1
+ else
+ main_status = 2
- // set channels depending on how much charge we have left
+ // Set channels depending on how much charge we have left
// Allow the APC to operate as normal if the cell can charge
if(charging && longtermpower < 10)
@@ -1249,8 +1270,8 @@
environ = autoset(environ, 1)
area.poweralert(0, src)
autoflag = 2
- else if(cell.charge < 750 && cell.charge > 10 && longtermpower < 0) // <15%, turn off lighting & equipment
- if(autoflag != 1)
+ else if(cell.charge < 750 && cell.charge > 10) // <15%, turn off lighting & equipment
+ if((autoflag > 1 && longtermpower < 0) || (autoflag > 1 && longtermpower >= 0))
equipment = autoset(equipment, 2)
lighting = autoset(lighting, 2)
environ = autoset(environ, 1)
@@ -1265,32 +1286,28 @@
autoflag = 0
// now trickle-charge the cell
-
- if(chargemode && charging == 1 && operating)
- if(excess > 0) // check to make sure we have enough to charge
- // Max charge is perapc share, capped to cell capacity, or % per second constant (Whichever is smallest)
- var/ch = min(perapc*CELLRATE, (cell_maxcharge - cell.charge), (cell_maxcharge*CHARGELEVEL))
- add_load(ch/CELLRATE) // Removes the power we're taking from the grid
- cell.give(ch) // actually recharge the cell
-
+ if(src.attempt_charging())
+ if (power_excess > 0) // check to make sure we have enough to charge
+ cell.give(power_excess*CELLRATE) // actually recharge the cell
else
charging = 0 // stop charging
chargecount = 0
// show cell as fully charged if so
-
if(cell.charge >= cell_maxcharge)
charging = 2
+ //if we have excess power for long enough, think about re-enable charging.
if(chargemode)
if(!charging)
- if(excess > cell_maxcharge*CHARGELEVEL)
+ //last_surplus() overestimates the amount of power available for charging, but it's equivalent to what APCs were doing before.
+ if(src.last_surplus()*CELLRATE >= cell_maxcharge*CHARGELEVEL)
chargecount++
else
chargecount = 0
+ charging = 0
- if(chargecount == 10)
-
+ if(chargecount >= 10)
chargecount = 0
charging = 1
@@ -1319,21 +1336,21 @@
src.updateDialog()
// val 0=off, 1=off(auto) 2=on 3=on(auto)
-// on 0=off, 1=on, 2=autooff
+// on 0=off, 1=auto-on, 2=auto-off
/proc/autoset(var/val, var/on)
- if(on==0)
+ if(on==0) // turn things off
if(val==2) // if on, return off
return 0
else if(val==3) // if auto-on, return auto-off
return 1
- else if(on==1)
+ else if(on==1) // turn things auto-on
if(val==1) // if auto-off, return auto-on
return 3
- else if(on==2)
+ else if(on==2) // turn things auto-off
if(val==3) // if auto-on, return auto-off
return 1
diff --git a/code/modules/power/batteryrack.dm b/code/modules/power/batteryrack.dm
new file mode 100644
index 00000000..e7a46b60
--- /dev/null
+++ b/code/modules/power/batteryrack.dm
@@ -0,0 +1,252 @@
+//Boards
+/obj/item/weapon/circuitboard/batteryrack
+ name = "Circuit board (Battery rack PSU)"
+ build_path = "/obj/machinery/power/smes/batteryrack"
+ board_type = "machine"
+ origin_tech = "powerstorage=3;engineering=2"
+ frame_desc = "Requires 3 power cells."
+ req_components = list("/obj/item/weapon/cell" = 3)
+
+
+/obj/item/weapon/circuitboard/ghettosmes
+ name = "Circuit board (makeshift PSU)"
+ desc = "An APC circuit repurposed into some power storage device controller"
+ build_path = "/obj/machinery/power/smes/batteryrack/makeshift"
+ board_type = "machine"
+ frame_desc = "Requires 3 power cells."
+ req_components = list("/obj/item/weapon/cell" = 3)
+
+
+//Machines
+//The one that works safely.
+/obj/machinery/power/smes/batteryrack
+ name = "power cell rack PSU"
+ desc = "A rack of power cells working as a PSU."
+ charge = 0 //you dont really want to make a potato PSU which already is overloaded
+ online = 0
+ chargelevel = 0
+ output = 0
+ input_level_max = 0
+ output_level_max = 0
+ icon_state = "gsmes"
+ var/cells_amount = 0
+ var/capacitors_amount = 0
+
+
+/obj/machinery/power/smes/batteryrack/New()
+ ..()
+ add_parts()
+ RefreshParts()
+ return
+
+
+//Maybe this should be moved up to obj/machinery
+/obj/machinery/power/smes/batteryrack/proc/add_parts()
+ component_parts = list()
+ component_parts += new /obj/item/weapon/circuitboard/batteryrack
+ component_parts += new /obj/item/weapon/cell/high
+ component_parts += new /obj/item/weapon/cell/high
+ component_parts += new /obj/item/weapon/cell/high
+ return
+
+
+/obj/machinery/power/smes/batteryrack/RefreshParts()
+ capacitors_amount = 0
+ cells_amount = 0
+ var/max_level = 0 //for both input and output
+ for(var/obj/item/weapon/stock_parts/capacitor/CP in component_parts)
+ max_level += CP.rating
+ capacitors_amount++
+ input_level_max = 50000 + max_level * 20000
+ output_level_max = 50000 + max_level * 20000
+
+ var/C = 0
+ for(var/obj/item/weapon/cell/PC in component_parts)
+ C += PC.maxcharge
+ cells_amount++
+ capacity = C * 40 //Basic cells are such crap. Hyper cells needed to get on normal SMES levels.
+
+
+/obj/machinery/power/smes/batteryrack/updateicon()
+ overlays.Cut()
+ if(stat & BROKEN) return
+
+ if (online)
+ overlays += image('icons/obj/power.dmi', "gsmes_outputting")
+ if(charging)
+ overlays += image('icons/obj/power.dmi', "gsmes_charging")
+
+ var/clevel = chargedisplay()
+ if(clevel>0)
+ overlays += image('icons/obj/power.dmi', "gsmes_og[clevel]")
+ return
+
+
+/obj/machinery/power/smes/batteryrack/chargedisplay()
+ return round(4 * charge/(capacity ? capacity : 5e6))
+
+
+/obj/machinery/power/smes/batteryrack/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) //these can only be moved by being reconstructed, solves having to remake the powernet.
+ ..() //SMES attackby for now handles screwdriver, cable coils and wirecutters, no need to repeat that here
+ if(open_hatch)
+ if(istype(W, /obj/item/weapon/crowbar))
+ if (charge < (capacity / 100))
+ if (!online && !chargemode)
+ playsound(get_turf(src), 'sound/items/Crowbar.ogg', 50, 1)
+ var/obj/machinery/constructable_frame/machine_frame/M = new /obj/machinery/constructable_frame/machine_frame(src.loc)
+ M.state = 2
+ M.icon_state = "box_1"
+ for(var/obj/I in component_parts)
+ if(I.reliability != 100 && crit_fail)
+ I.crit_fail = 1
+ I.loc = src.loc
+ del(src)
+ return 1
+ else
+ user << " Turn off the [src] before dismantling it."
+ else
+ user << " Better let [src] discharge before dismantling it."
+ else if ((istype(W, /obj/item/weapon/stock_parts/capacitor) && (capacitors_amount < 5)) || (istype(W, /obj/item/weapon/cell) && (cells_amount < 5)))
+ if (charge < (capacity / 100))
+ if (!online && !chargemode)
+ user.drop_item()
+ component_parts += W
+ W.loc = src
+ RefreshParts()
+ user << " You upgrade the [src] with [W.name]."
+ else
+ user << " Turn off the [src] before dismantling it."
+ else
+ user << " Better let [src] discharge before putting your hand inside it."
+ else
+ user.set_machine(src)
+ interact(user)
+ return 1
+ return
+
+
+//The shitty one that will blow up.
+/obj/machinery/power/smes/batteryrack/makeshift
+ name = "makeshift PSU"
+ desc = "A rack of batteries connected by a mess of wires posing as a PSU."
+ var/overcharge_percent = 0
+
+
+/obj/machinery/power/smes/batteryrack/makeshift/add_parts()
+ component_parts = list()
+ component_parts += new /obj/item/weapon/circuitboard/ghettosmes
+ component_parts += new /obj/item/weapon/cell/high
+ component_parts += new /obj/item/weapon/cell/high
+ component_parts += new /obj/item/weapon/cell/high
+ return
+
+
+/obj/machinery/power/smes/batteryrack/makeshift/updateicon()
+ overlays.Cut()
+ if(stat & BROKEN) return
+
+ if (online)
+ overlays += image('icons/obj/power.dmi', "gsmes_outputting")
+ if(charging)
+ overlays += image('icons/obj/power.dmi', "gsmes_charging")
+ if (overcharge_percent > 100)
+ overlays += image('icons/obj/power.dmi', "gsmes_overcharge")
+ else
+ var/clevel = chargedisplay()
+ if(clevel>0)
+ overlays += image('icons/obj/power.dmi', "gsmes_og[clevel]")
+ return
+
+//This mess of if-elses and magic numbers handles what happens if the engies don't pay attention and let it eat too much charge
+//What happens depends on how much capacity has the ghetto smes and how much it is overcharged.
+//Under 1.2M: 5% of ion_act() per process() tick from 125% and higher overcharges. 1.2M is achieved with 3 high cells.
+//[1.2M-2.4M]: 6% ion_act from 120%. 1% of EMP from 140%.
+//(2.4M-3.6M] :7% ion_act from 115%. 1% of EMP from 130%. 1% of non-hull-breaching explosion at 150%.
+//(3.6M-INFI): 8% ion_act from 115%. 2% of EMP from 125%. 1% of Hull-breaching explosion from 140%.
+/obj/machinery/power/smes/batteryrack/makeshift/proc/overcharge_consequences()
+ switch (capacity)
+ if (0 to (1.2e6-1))
+ if (overcharge_percent >= 125)
+ if (prob(5))
+ ion_act()
+ if (1.2e6 to 2.4e6)
+ if (overcharge_percent >= 120)
+ if (prob(6))
+ ion_act()
+ else
+ return
+ if (overcharge_percent >= 140)
+ if (prob(1))
+ empulse(src.loc, 3, 8, 1)
+ if ((2.4e6+1) to 3.6e6)
+ if (overcharge_percent >= 115)
+ if (prob(7))
+ ion_act()
+ else
+ return
+ if (overcharge_percent >= 130)
+ if (prob(1))
+ empulse(src.loc, 3, 8, 1)
+ if (overcharge_percent >= 150)
+ if (prob(1))
+ explosion(src.loc, 0, 1, 3, 5)
+ if ((3.6e6+1) to INFINITY)
+ if (overcharge_percent >= 115)
+ if (prob(8))
+ ion_act()
+ else
+ return
+ if (overcharge_percent >= 125)
+ if (prob(2))
+ empulse(src.loc, 4, 10, 1)
+ if (overcharge_percent >= 140)
+ if (prob(1))
+ explosion(src.loc, 1, 3, 5, 8)
+ else //how the hell was this proc called for negative charge
+ charge = 0
+
+
+#define SMESRATE 0.05 // rate of internal charge to external power
+/obj/machinery/power/smes/batteryrack/makeshift/process()
+ if(stat & BROKEN) return
+
+ //store machine state to see if we need to update the icon overlays
+ var/last_disp = chargedisplay()
+ var/last_chrg = charging
+ var/last_onln = online
+ var/last_overcharge = overcharge_percent
+
+ if(terminal)
+ var/excess = terminal.surplus()
+
+ if(charging)
+ if(excess >= 0) // if there's power available, try to charge
+ var/load = min((capacity * 1.5 - charge)/SMESRATE, chargelevel) // charge at set rate, limited to spare capacity
+ load = add_load(load) // add the load to the terminal side network
+ charge += load * SMESRATE // increase the charge
+
+
+ else // if not enough capacity
+ charging = 0 // stop charging
+
+ else
+ if (chargemode && excess > 0 && excess >= chargelevel)
+ charging = 1
+
+ if(online) // if outputting
+ lastout = min( charge/SMESRATE, output) //limit output to that stored
+ charge -= lastout*SMESRATE // reduce the storage (may be recovered in /restore() if excessive)
+ add_avail(lastout) // add output to powernet (smes side)
+ if(charge < 0.0001)
+ online = 0 // stop output if charge falls to zero
+
+ overcharge_percent = round((charge / capacity) * 100)
+ if (overcharge_percent > 115) //115% is the minimum overcharge for anything to happen
+ overcharge_consequences()
+
+ // only update icon if state changed
+ if(last_disp != chargedisplay() || last_chrg != charging || last_onln != online || ((overcharge_percent > 100) ^ (last_overcharge > 100)))
+ updateicon()
+ return
+
+#undef SMESRATE
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index b39973d7..681b4643 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -212,8 +212,7 @@
w_class = 2.0
throw_speed = 2
throw_range = 5
- m_amt = 50
- g_amt = 20
+ matter = list("metal" = 50,"glass" = 20)
flags = TABLEPASS | FPRINT | CONDUCT
slot_flags = SLOT_BELT
item_state = "coil"
@@ -229,6 +228,8 @@
src.amount = length
if (param_color)
color = param_color
+ else
+ color = item_color
pixel_x = rand(-2,2)
pixel_y = rand(-2,2)
updateicon()
@@ -237,6 +238,7 @@
/obj/item/weapon/cable_coil/proc/updateicon()
if (!color)
color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_ORANGE, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
+ item_color = color
if(amount == 1)
icon_state = "coil1"
name = "cable piece"
@@ -306,7 +308,7 @@
return
else
- user << " You transfer [MAXCOIL - src.amount ] length\s of cable from one coil to the other."
+ user << " You transfer [MAXCOIL - C.amount ] length\s of cable from one coil to the other."
src.amount -= (MAXCOIL-C.amount)
src.updateicon()
src.update_wclass()
diff --git a/code/modules/power/cable_logic.dm b/code/modules/power/cable_logic.dm
index a7d2316c..3b5b67a3 100644
--- a/code/modules/power/cable_logic.dm
+++ b/code/modules/power/cable_logic.dm
@@ -85,7 +85,7 @@
var/dir_output = 1
var/obj/structure/cable/input
var/obj/structure/cable/output
- icon = 'icons/obj/pipes/heat.dmi'
+ icon = 'icons/atmos/heat.dmi'
icon_state = "intact"
/obj/machinery/logic/oneinput/process()
@@ -130,7 +130,7 @@
if( !(pn_input.avail >= LOGIC_HIGH))
pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
else
- pn_output.newload += LOGIC_HIGH //Otherwise increase the load to 5
+ pn_output.draw_power(LOGIC_HIGH) //Otherwise increase the load to 5
@@ -202,7 +202,7 @@
if( (pn_input1.avail >= LOGIC_HIGH) && (pn_input2.avail >= LOGIC_HIGH) )
pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
else
- pn_output.newload += LOGIC_HIGH //Otherwise increase the load to 5
+ pn_output.draw_power(LOGIC_HIGH) //Otherwise increase the load to 5
//OR GATE
/obj/machinery/logic/twoinput/or/process()
@@ -222,7 +222,7 @@
if( (pn_input1.avail >= LOGIC_HIGH) || (pn_input2.avail >= LOGIC_HIGH) )
pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
else
- pn_output.newload += LOGIC_HIGH //Otherwise increase the load to 5
+ pn_output.draw_power(LOGIC_HIGH) //Otherwise increase the load to 5
//XOR GATE
/obj/machinery/logic/twoinput/xor/process()
@@ -242,7 +242,7 @@
if( (pn_input1.avail >= LOGIC_HIGH) != (pn_input2.avail >= LOGIC_HIGH) )
pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
else
- pn_output.newload += LOGIC_HIGH //Otherwise increase the load to 5
+ pn_output.draw_power(LOGIC_HIGH) //Otherwise increase the load to 5
//XNOR GATE (EQUIVALENCE)
/obj/machinery/logic/twoinput/xnor/process()
@@ -262,7 +262,7 @@
if( (pn_input1.avail >= LOGIC_HIGH) == (pn_input2.avail >= LOGIC_HIGH) )
pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
else
- pn_output.newload += LOGIC_HIGH //Otherwise increase the load to 5
+ pn_output.draw_power(LOGIC_HIGH) //Otherwise increase the load to 5
#define RELAY_POWER_TRANSFER 2000 //How much power a relay transfers through.
@@ -284,7 +284,7 @@
return
if(pn_input2.avail >= RELAY_POWER_TRANSFER)
- pn_input2.newload += RELAY_POWER_TRANSFER
+ pn_input2.draw_power(RELAY_POWER_TRANSFER)
pn_output.newavail += RELAY_POWER_TRANSFER
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index 1d374f73..fd9fdfbf 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -22,6 +22,9 @@
/obj/item/weapon/cell/proc/percent() // return % charge of cell
return 100.0*charge/maxcharge
+/obj/item/weapon/cell/proc/fully_charged()
+ return (charge == maxcharge)
+
// use power from a cell
/obj/item/weapon/cell/proc/use(var/amount)
if(rigged && amount > 0)
@@ -39,15 +42,15 @@
return 0
if(maxcharge < amount) return 0
- var/power_used = min(maxcharge-charge,amount)
+ var/amount_used = min(maxcharge-charge,amount)
if(crit_fail) return 0
if(!prob(reliability))
minor_fault++
if(prob(minor_fault))
crit_fail = 1
return 0
- charge += power_used
- return power_used
+ charge += amount_used
+ return amount_used
/obj/item/weapon/cell/examine()
diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm
index 24391046..1ae2795f 100644
--- a/code/modules/power/generator.dm
+++ b/code/modules/power/generator.dm
@@ -84,15 +84,17 @@
air2.temperature = air2.temperature + heat/air2_heat_capacity
air1.temperature = air1.temperature - energy_transfer/air1_heat_capacity
- //Transfer the air
- circ1.air2.merge(air1)
- circ2.air2.merge(air2)
+ //Transfer the air
+ if (air1)
+ circ1.air2.merge(air1)
+ if (air2)
+ circ2.air2.merge(air2)
- //Update the gas networks
- if(circ1.network2)
- circ1.network2.update = 1
- if(circ2.network2)
- circ2.network2.update = 1
+ //Update the gas networks
+ if(circ1.network2)
+ circ1.network2.update = 1
+ if(circ2.network2)
+ circ2.network2.update = 1
// update icon overlays and power usage only if displayed level has changed
if(lastgen > 250000 && prob(10))
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index 2197fc5c..e5d0e529 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -34,7 +34,7 @@
var/ndir = get_dir(usr,on_wall)
if (!(ndir in cardinal))
return
- var/turf/loc = get_turf_loc(usr)
+ var/turf/loc = get_turf(usr)
if (!istype(loc, /turf/simulated/floor))
usr << "\red [src.name] cannot be placed on this spot."
return
@@ -357,6 +357,7 @@
switchcount = L.switchcount
rigged = L.rigged
brightness = L.brightness
+ l_color = L.color
on = has_power()
update()
@@ -516,6 +517,7 @@
L.status = status
L.rigged = rigged
L.brightness = src.brightness
+ L.color = l_color
// light item inherits the switchcount, then zero it
L.switchcount = switchcount
@@ -541,6 +543,7 @@
L.status = status
L.rigged = rigged
L.brightness = brightness
+ L.color = l_color
// light item inherits the switchcount, then zero it
L.switchcount = switchcount
@@ -648,7 +651,7 @@
var/status = 0 // LIGHT_OK, LIGHT_BURNED or LIGHT_BROKEN
var/base_state
var/switchcount = 0 // number of times switched
- m_amt = 60
+ matter = list("metal" = 60)
var/rigged = 0 // true if rigged to explode
var/brightness = 2 //how much light it gives off
@@ -658,7 +661,7 @@
icon_state = "ltube"
base_state = "ltube"
item_state = "c_tube"
- g_amt = 100
+ matter = list("glass" = 100)
brightness = 8
/obj/item/weapon/light/tube/large
@@ -672,7 +675,7 @@
icon_state = "lbulb"
base_state = "lbulb"
item_state = "contvapour"
- g_amt = 100
+ matter = list("glass" = 100)
brightness = 5
/obj/item/weapon/light/throw_impact(atom/hit_atom)
@@ -685,7 +688,7 @@
icon_state = "fbulb"
base_state = "fbulb"
item_state = "egg4"
- g_amt = 100
+ matter = list("glass" = 100)
brightness = 5
// update the icon state and description of the light
diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm
index f8d8aea4..86f34b49 100644
--- a/code/modules/power/port_gen.dm
+++ b/code/modules/power/port_gen.dm
@@ -42,7 +42,7 @@ display round(lastgen) and plasmatank amount
//Baseline portable generator. Has all the default handling. Not intended to be used on it's own (since it generates unlimited power).
/obj/machinery/power/port_gen
- name = "Portable Generator"
+ name = "Placeholder Generator" //seriously, don't use this. It can't be anchored without VV magic.
desc = "A portable generator for emergency backup power"
icon = 'icons/obj/power.dmi'
icon_state = "portgen0"
diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm
index 31932ca0..c044742a 100644
--- a/code/modules/power/power.dm
+++ b/code/modules/power/power.dm
@@ -20,11 +20,12 @@
/obj/machinery/power/proc/add_load(var/amount)
if(powernet)
- powernet.newload += amount
+ return powernet.draw_power(amount)
+ return 0
/obj/machinery/power/proc/surplus()
if(powernet)
- return powernet.avail-powernet.load
+ return powernet.surplus()
else
return 0
@@ -42,8 +43,10 @@
if(!src.loc)
return 0
- if(!use_power)
- return 1
+ //This is bad. This makes machines which are switched off not update their stat flag correctly when power_change() is called.
+ //If use_power is 0, then you probably shouldn't be checking power to begin with.
+ //if(!use_power)
+ // return 1
var/area/A = src.loc.loc // make sure it's in an area
if(!A || !isarea(A) || !A.master)
@@ -62,18 +65,23 @@
chan = power_channel
A.master.use_power(amount, chan)
if(!autocalled)
+ log_power_update_request(A.master, src)
A.master.powerupdate = 2 // Decremented by 2 each GC tick, since it's not auto power change we're going to update power twice.
-/obj/machinery/proc/power_change() // called whenever the power settings of the containing area change
+//The master_area optional argument can be used to save on a lot of processing if the master area is already known. This is mainly intended for when this proc is called by the master controller.
+/obj/machinery/proc/power_change(var/area/master_area = null) // called whenever the power settings of the containing area change
// by default, check equipment channel & set flag
// can override if needed
- if(powered(power_channel))
+ var/has_power
+ if (master_area)
+ has_power = master_area.powered(power_channel)
+ else
+ has_power = powered(power_channel)
+
+ if(has_power)
stat &= ~NOPOWER
else
-
stat |= NOPOWER
- return
-
// the powernet datum
// each contiguous network of cables & nodes
@@ -222,234 +230,6 @@
. += C
return .
-
-/proc/powernet_nextlink(var/obj/O, var/datum/powernet/PN)
- var/list/P
-
- while(1)
- if( istype(O,/obj/structure/cable) )
- var/obj/structure/cable/C = O
- C.powernet = PN
- P = C.get_connections()
-
- else if(O.anchored && istype(O,/obj/machinery/power))
- var/obj/machinery/power/M = O
- M.powernet = PN
- P = M.get_connections()
-
- else
- return
-
- if(P.len == 0) return
-
- O = P[1]
-
- for(var/L = 2 to P.len)
- powernet_nextlink(P[L], PN)
-
-
-// cut a powernet at this cable object
-/datum/powernet/proc/cut_cable(var/obj/structure/cable/C)
- var/turf/T1 = C.loc
- if(!T1) return
- var/node = 0
- if(C.d1 == 0)
- node = 1
-
- var/turf/T2
- if(C.d2) T2 = get_step(T1, C.d2)
- if(C.d1) T1 = get_step(T1, C.d1)
-
-
- var/list/P1 = power_list(T1, C, C.d1) // what joins on to cut cable in dir1
- var/list/P2 = power_list(T2, C, C.d2) // what joins on to cut cable in dir2
-
-// if(Debug)
-// for(var/obj/O in P1)
-// world.log << "P1: [O] at [O.x] [O.y] : [istype(O, /obj/structure/cable) ? "[O:d1]/[O:d2]" : null] "
-// for(var/obj/O in P2)
-// world.log << "P2: [O] at [O.x] [O.y] : [istype(O, /obj/structure/cable) ? "[O:d1]/[O:d2]" : null] "
-
-
- if(P1.len == 0 || P2.len == 0)//if nothing in either list, then the cable was an endpoint no need to rebuild the powernet,
- cables -= C //just remove cut cable from the list
-// if(Debug) world.log << "Was end of cable"
- return
-
- //null the powernet reference of all cables & nodes in this powernet
- var/i=1
- while(i<=cables.len)
- var/obj/structure/cable/Cable = cables[i]
- if(Cable)
- Cable.powernet = null
- if(Cable == C)
- cables.Cut(i,i+1)
- continue
- i++
- i=1
- while(i<=nodes.len)
- var/obj/machinery/power/Node = nodes[i]
- if(Node)
- Node.powernet = null
- i++
-
- // remove the cut cable from the network
-// C.netnum = -1
-
- C.loc = null
-
- powernet_nextlink(P1[1], src) // propagate network from 1st side of cable, using current netnum //TODO?
-
- // now test to see if propagation reached to the other side
- // if so, then there's a loop in the network
- var/notlooped = 0
- for(var/O in P2)
- if( istype(O, /obj/machinery/power) )
- var/obj/machinery/power/Machine = O
- if(Machine.powernet != src)
- notlooped = 1
- break
- else if( istype(O, /obj/structure/cable) )
- var/obj/structure/cable/Cable = O
- if(Cable.powernet != src)
- notlooped = 1
- break
-
- if(notlooped)
- // not looped, so make a new powernet
- var/datum/powernet/PN = new()
- powernets += PN
-
-// if(Debug) world.log << "Was not looped: spliting PN#[number] ([cables.len];[nodes.len])"
-
- i=1
- while(i<=cables.len)
- var/obj/structure/cable/Cable = cables[i]
- if(Cable && !Cable.powernet) // non-connected cables will have powernet=null, since they weren't reached by propagation
- Cable.powernet = PN
- cables.Cut(i,i+1) // remove from old network & add to new one
- PN.cables += Cable
- continue
- i++
-
- i=1
- while(i<=nodes.len)
- var/obj/machinery/power/Node = nodes[i]
- if(Node && !Node.powernet)
- Node.powernet = PN
- nodes.Cut(i,i+1)
- PN.nodes[Node] = Node
- continue
- i++
-
- // Disconnect machines connected to nodes
- if(node)
- for(var/obj/machinery/power/P in T1)
- if(P.powernet && !P.powernet.nodes[src])
- P.disconnect_from_network()
-// if(Debug)
-// world.log << "Old PN#[number] : ([cables.len];[nodes.len])"
-// world.log << "New PN#[PN.number] : ([PN.cables.len];[PN.nodes.len])"
-//
-// else
-// if(Debug)
-// world.log << "Was looped."
-// //there is a loop, so nothing to be done
-// return
-
-
-
-/datum/powernet/proc/reset()
- load = newload
- newload = 0
- avail = newavail
- newavail = 0
-
-
- viewload = 0.8*viewload + 0.2*load
-
- viewload = round(viewload)
-
- var/numapc = 0
-
- if(nodes && nodes.len) // Added to fix a bad list bug -- TLE
- for(var/obj/machinery/power/terminal/term in nodes)
- if( istype( term.master, /obj/machinery/power/apc ) )
- numapc++
-
- if(numapc)
- perapc = avail/numapc
-
- netexcess = avail - load
-
- if( netexcess > 100) // if there was excess power last cycle
- if(nodes && nodes.len)
- for(var/obj/machinery/power/smes/S in nodes) // find the SMESes in the network
- if(S.powernet == src)
- S.restore() // and restore some of the power that was used
- else
- error("[S.name] (\ref[S]) had a [S.powernet ? "different (\ref[S.powernet])" : "null"] powernet to our powernet (\ref[src]).")
- nodes.Remove(S)
-
-/datum/powernet/proc/get_electrocute_damage()
- switch(avail)/*
- if (1300000 to INFINITY)
- return min(rand(70,150),rand(70,150))
- if (750000 to 1300000)
- return min(rand(50,115),rand(50,115))
- if (100000 to 750000-1)
- return min(rand(35,101),rand(35,101))
- if (75000 to 100000-1)
- return min(rand(30,95),rand(30,95))
- if (50000 to 75000-1)
- return min(rand(25,80),rand(25,80))
- if (25000 to 50000-1)
- return min(rand(20,70),rand(20,70))
- if (10000 to 25000-1)
- return min(rand(20,65),rand(20,65))
- if (1000 to 10000-1)
- return min(rand(10,20),rand(10,20))*/
- if (1000000 to INFINITY)
- return min(rand(50,160),rand(50,160))
- if (200000 to 1000000)
- return min(rand(25,80),rand(25,80))
- if (100000 to 200000)//Ave powernet
- return min(rand(20,60),rand(20,60))
- if (50000 to 100000)
- return min(rand(15,40),rand(15,40))
- if (1000 to 50000)
- return min(rand(10,20),rand(10,20))
- else
- return 0
-
-//The powernet that calls this proc will consume the other powernet - Rockdtben
-//TODO: rewrite so the larger net absorbs the smaller net
-/proc/merge_powernets(var/datum/powernet/net1, var/datum/powernet/net2)
- if(!net1 || !net2) return
- if(net1 == net2) return
-
- //We assume net1 is larger. If net2 is in fact larger we are just going to make them switch places to reduce on code.
- if(net1.cables.len < net2.cables.len) //net2 is larger than net1. Let's switch them around
- var/temp = net1
- net1 = net2
- net2 = temp
-
- for(var/i=1,i<=net2.nodes.len,i++) //merge net2 into net1
- var/obj/machinery/power/Node = net2.nodes[i]
- if(Node)
- Node.powernet = net1
- net1.nodes[Node] = Node
-
- for(var/i=1,i<=net2.cables.len,i++)
- var/obj/structure/cable/Cable = net2.cables[i]
- if(Cable)
- Cable.powernet = net1
- net1.cables += Cable
-
- del(net2)
- return net1
-
-
/obj/machinery/power/proc/connect_to_network()
var/turf/T = src.loc
var/obj/structure/cable/C = T.get_cable_node()
@@ -490,6 +270,9 @@
//No animations will be performed by this proc.
/proc/electrocute_mob(mob/living/carbon/M as mob, var/power_source, var/obj/source, var/siemens_coeff = 1.0)
if(istype(M.loc,/obj/mecha)) return 0 //feckin mechs are dumb
+
+ //This is for performance optimization only.
+ //DO NOT modify siemens_coeff here. That is checked in human/electrocute_act()
if(istype(M,/mob/living/carbon/human))
var/mob/living/carbon/human/H = M
if(H.gloves)
@@ -540,11 +323,11 @@
var/drained_energy = drained_hp*20
if (source_area)
- source_area.use_power(drained_energy/CELLRATE)
+ source_area.use_power(drained_energy)
else if (istype(power_source,/datum/powernet))
- var/drained_power = drained_energy/CELLRATE //convert from "joules" to "watts"
- PN.newload+=drained_power
+ //var/drained_power = drained_energy/CELLRATE //convert from "joules" to "watts" <<< NO. THIS IS WRONG. CELLRATE DOES NOT CONVERT TO OR FROM JOULES.
+ PN.draw_power(drained_energy)
else if (istype(power_source, /obj/item/weapon/cell))
- cell.use(drained_energy)
+ cell.use(drained_energy*CELLRATE) //convert to units of charge.
return drained_energy
diff --git a/code/modules/power/power_monitor.dm b/code/modules/power/power_monitor.dm
new file mode 100644
index 00000000..e1920471
--- /dev/null
+++ b/code/modules/power/power_monitor.dm
@@ -0,0 +1,136 @@
+// the power monitoring computer
+// for the moment, just report the status of all APCs in the same powernet
+/obj/machinery/power/monitor
+ name = "power monitoring computer"
+ desc = "It monitors power levels across the station."
+ icon = 'icons/obj/computer.dmi'
+ icon_state = "power"
+
+ //computer stuff
+ density = 1
+ anchored = 1.0
+ var/circuit = /obj/item/weapon/circuitboard/powermonitor
+ use_power = 1
+ idle_power_usage = 300
+ active_power_usage = 300
+
+/obj/machinery/power/monitor/New()
+ ..()
+ var/obj/structure/cable/attached = null
+ var/turf/T = loc
+ if(isturf(T))
+ attached = locate() in T
+ if(attached)
+ powernet = attached.get_powernet()
+
+/obj/machinery/power/monitor/attack_ai(mob/user)
+ add_fingerprint(user)
+
+ if(stat & (BROKEN|NOPOWER))
+ return
+ interact(user)
+
+/obj/machinery/power/monitor/attack_hand(mob/user)
+ add_fingerprint(user)
+
+ if(stat & (BROKEN|NOPOWER))
+ return
+ interact(user)
+
+/obj/machinery/power/monitor/interact(mob/user)
+
+ if ( (get_dist(src, user) > 1 ) || (stat & (BROKEN|NOPOWER)) )
+ if (!istype(user, /mob/living/silicon))
+ user.unset_machine()
+ user << browse(null, "window=powcomp")
+ return
+
+
+ user.set_machine(src)
+ var/t = " Power Monitoring "
+
+ t += "
Refresh"
+ t += "
Close"
+
+ if(!powernet)
+ t += "\red No connection"
+ else
+
+ var/list/L = list()
+ for(var/obj/machinery/power/terminal/term in powernet.nodes)
+ if(istype(term.master, /obj/machinery/power/apc))
+ var/obj/machinery/power/apc/A = term.master
+ L += A
+
+ t += "Total power: [powernet.avail] W Total load: [num2text(powernet.viewload,10)] W "
+
+ t += ""
+
+ if(L.len > 0)
+ var/total_demand = 0
+ t += "Area Eqp./Lgt./Env. Load Cell "
+
+ var/list/S = list(" Off","AOff"," On", " AOn")
+ var/list/chg = list("N","C","F")
+
+ for(var/obj/machinery/power/apc/A in L)
+
+ t += copytext(add_tspace("\The [A.area]", 30), 1, 30)
+ t += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_lspace(A.lastused_total, 6)] [A.cell ? "[add_lspace(round(A.cell.percent()), 3)]% [chg[A.charging+1]]" : " N/C"] "
+ total_demand += A.lastused_total
+
+ t += " Total demand: [total_demand] W"
+ t += " "
+
+ user << browse(t, "window=powcomp;size=420x900")
+ onclose(user, "powcomp")
+
+
+/obj/machinery/power/monitor/Topic(href, href_list)
+ ..()
+ if( href_list["close"] )
+ usr << browse(null, "window=powcomp")
+ usr.unset_machine()
+ return
+ if( href_list["update"] )
+ src.updateDialog()
+ return
+
+
+/obj/machinery/power/monitor/power_change()
+ ..()
+ if(stat & BROKEN)
+ icon_state = "broken"
+ else
+ if (stat & NOPOWER)
+ spawn(rand(0, 15))
+ src.icon_state = "c_unpowered"
+ else
+ icon_state = initial(icon_state)
+
+
+//copied from computer.dm
+/obj/machinery/power/monitor/attackby(I as obj, user as mob)
+ if(istype(I, /obj/item/weapon/screwdriver) && circuit)
+ playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
+ if(do_after(user, 20))
+ var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc )
+ var/obj/item/weapon/circuitboard/M = new circuit( A )
+ A.circuit = M
+ A.anchored = 1
+ for (var/obj/C in src)
+ C.loc = src.loc
+ if (src.stat & BROKEN)
+ user << "\blue The broken glass falls out."
+ new /obj/item/weapon/shard( src.loc )
+ A.state = 3
+ A.icon_state = "3"
+ else
+ user << "\blue You disconnect the monitor."
+ A.state = 4
+ A.icon_state = "4"
+ M.deconstruct(src)
+ del(src)
+ else
+ src.attack_hand(user)
+ return
\ No newline at end of file
diff --git a/code/modules/power/powernet.dm b/code/modules/power/powernet.dm
new file mode 100644
index 00000000..1fbcc6f1
--- /dev/null
+++ b/code/modules/power/powernet.dm
@@ -0,0 +1,265 @@
+/datum/powernet
+ var/list/cables = list() // all cables & junctions
+ var/list/nodes = list() // all APCs & sources
+
+ var/newload = 0
+ var/load = 0
+ var/newavail = 0
+ var/avail = 0
+ var/viewload = 0
+ var/number = 0
+
+ var/perapc = 0 // per-apc avilability
+ var/perapc_excess = 0
+ var/netexcess = 0
+
+/datum/powernet/proc/process()
+ load = newload
+ newload = 0
+ avail = newavail
+ newavail = 0
+
+
+ viewload = 0.8*viewload + 0.2*load
+
+ viewload = round(viewload)
+
+ var/numapc = 0
+
+ if(nodes && nodes.len) // Added to fix a bad list bug -- TLE
+ for(var/obj/machinery/power/terminal/term in nodes)
+ if( istype( term.master, /obj/machinery/power/apc ) )
+ numapc++
+
+ netexcess = avail - load
+
+ if(numapc)
+ //very simple load balancing. If there was a net excess this tick then it must have been that some APCs used less than perapc, since perapc*numapc = avail
+ //Therefore we can raise the amount of power rationed out to APCs on the assumption that those APCs that used less than perapc will continue to do so.
+ //If that assumption fails, then some APCs will miss out on power next tick, however it will be rebalanced for the tick after.
+ if (netexcess >= 0)
+ perapc_excess += min(netexcess/numapc, (avail - perapc) - perapc_excess)
+ else
+ perapc_excess = 0
+
+ perapc = avail/numapc + perapc_excess
+
+ if( netexcess > 100) // if there was excess power last cycle
+ if(nodes && nodes.len)
+ for(var/obj/machinery/power/smes/S in nodes) // find the SMESes in the network
+ if(S.powernet == src)
+ S.restore() // and restore some of the power that was used
+ else
+ error("[S.name] (\ref[S]) had a [S.powernet ? "different (\ref[S.powernet])" : "null"] powernet to our powernet (\ref[src]).")
+ nodes.Remove(S)
+
+
+
+
+//Returns the amount of available power
+/datum/powernet/proc/surplus()
+ return max(avail - newload, 0)
+
+//Returns the amount of excess power (before refunding to SMESs) from last tick.
+//This is for machines that might adjust their power consumption using this data.
+/datum/powernet/proc/last_surplus()
+ return max(avail - load, 0)
+
+//Attempts to draw power from a powernet. Returns the actual amount of power drawn
+/datum/powernet/proc/draw_power(var/requested_amount)
+ var/surplus = max(avail - newload, 0)
+ var/actual_draw = min(requested_amount, surplus)
+ newload += actual_draw
+
+ return actual_draw
+
+// cut a powernet at this cable object
+/datum/powernet/proc/cut_cable(var/obj/structure/cable/C)
+ var/turf/T1 = C.loc
+ if(!T1) return
+ var/node = 0
+ if(C.d1 == 0)
+ node = 1
+
+ var/turf/T2
+ if(C.d2) T2 = get_step(T1, C.d2)
+ if(C.d1) T1 = get_step(T1, C.d1)
+
+
+ var/list/P1 = power_list(T1, C, C.d1) // what joins on to cut cable in dir1
+ var/list/P2 = power_list(T2, C, C.d2) // what joins on to cut cable in dir2
+
+// if(Debug)
+// for(var/obj/O in P1)
+// world.log << "P1: [O] at [O.x] [O.y] : [istype(O, /obj/structure/cable) ? "[O:d1]/[O:d2]" : null] "
+// for(var/obj/O in P2)
+// world.log << "P2: [O] at [O.x] [O.y] : [istype(O, /obj/structure/cable) ? "[O:d1]/[O:d2]" : null] "
+
+
+ if(P1.len == 0 || P2.len == 0)//if nothing in either list, then the cable was an endpoint no need to rebuild the powernet,
+ cables -= C //just remove cut cable from the list
+// if(Debug) world.log << "Was end of cable"
+ return
+
+ //null the powernet reference of all cables & nodes in this powernet
+ var/i=1
+ while(i<=cables.len)
+ var/obj/structure/cable/Cable = cables[i]
+ if(Cable)
+ Cable.powernet = null
+ if(Cable == C)
+ cables.Cut(i,i+1)
+ continue
+ i++
+ i=1
+ while(i<=nodes.len)
+ var/obj/machinery/power/Node = nodes[i]
+ if(Node)
+ Node.powernet = null
+ i++
+
+ // remove the cut cable from the network
+// C.netnum = -1
+
+ C.loc = null
+
+ powernet_nextlink(P1[1], src) // propagate network from 1st side of cable, using current netnum //TODO?
+
+ // now test to see if propagation reached to the other side
+ // if so, then there's a loop in the network
+ var/notlooped = 0
+ for(var/O in P2)
+ if( istype(O, /obj/machinery/power) )
+ var/obj/machinery/power/Machine = O
+ if(Machine.powernet != src)
+ notlooped = 1
+ break
+ else if( istype(O, /obj/structure/cable) )
+ var/obj/structure/cable/Cable = O
+ if(Cable.powernet != src)
+ notlooped = 1
+ break
+
+ if(notlooped)
+ // not looped, so make a new powernet
+ var/datum/powernet/PN = new()
+ powernets += PN
+
+// if(Debug) world.log << "Was not looped: spliting PN#[number] ([cables.len];[nodes.len])"
+
+ i=1
+ while(i<=cables.len)
+ var/obj/structure/cable/Cable = cables[i]
+ if(Cable && !Cable.powernet) // non-connected cables will have powernet=null, since they weren't reached by propagation
+ Cable.powernet = PN
+ cables.Cut(i,i+1) // remove from old network & add to new one
+ PN.cables += Cable
+ continue
+ i++
+
+ i=1
+ while(i<=nodes.len)
+ var/obj/machinery/power/Node = nodes[i]
+ if(Node && !Node.powernet)
+ Node.powernet = PN
+ nodes.Cut(i,i+1)
+ PN.nodes[Node] = Node
+ continue
+ i++
+
+ // Disconnect machines connected to nodes
+ if(node)
+ for(var/obj/machinery/power/P in T1)
+ if(P.powernet && !P.powernet.nodes[src])
+ P.disconnect_from_network()
+// if(Debug)
+// world.log << "Old PN#[number] : ([cables.len];[nodes.len])"
+// world.log << "New PN#[PN.number] : ([PN.cables.len];[PN.nodes.len])"
+//
+// else
+// if(Debug)
+// world.log << "Was looped."
+// //there is a loop, so nothing to be done
+// return
+
+/datum/powernet/proc/get_electrocute_damage()
+ switch(avail)/*
+ if (1300000 to INFINITY)
+ return min(rand(70,150),rand(70,150))
+ if (750000 to 1300000)
+ return min(rand(50,115),rand(50,115))
+ if (100000 to 750000-1)
+ return min(rand(35,101),rand(35,101))
+ if (75000 to 100000-1)
+ return min(rand(30,95),rand(30,95))
+ if (50000 to 75000-1)
+ return min(rand(25,80),rand(25,80))
+ if (25000 to 50000-1)
+ return min(rand(20,70),rand(20,70))
+ if (10000 to 25000-1)
+ return min(rand(20,65),rand(20,65))
+ if (1000 to 10000-1)
+ return min(rand(10,20),rand(10,20))*/
+ if (1000000 to INFINITY)
+ return min(rand(50,160),rand(50,160))
+ if (200000 to 1000000)
+ return min(rand(25,80),rand(25,80))
+ if (100000 to 200000)//Ave powernet
+ return min(rand(20,60),rand(20,60))
+ if (50000 to 100000)
+ return min(rand(15,40),rand(15,40))
+ if (1000 to 50000)
+ return min(rand(10,20),rand(10,20))
+ else
+ return 0
+
+/proc/powernet_nextlink(var/obj/O, var/datum/powernet/PN)
+ var/list/P
+
+ while(1)
+ if( istype(O,/obj/structure/cable) )
+ var/obj/structure/cable/C = O
+ C.powernet = PN
+ P = C.get_connections()
+
+ else if(O.anchored && istype(O,/obj/machinery/power))
+ var/obj/machinery/power/M = O
+ M.powernet = PN
+ P = M.get_connections()
+
+ else
+ return
+
+ if(P.len == 0) return
+
+ O = P[1]
+
+ for(var/L = 2 to P.len)
+ powernet_nextlink(P[L], PN)
+
+//The powernet that calls this proc will consume the other powernet - Rockdtben
+//TODO: rewrite so the larger net absorbs the smaller net
+/proc/merge_powernets(var/datum/powernet/net1, var/datum/powernet/net2)
+ if(!net1 || !net2) return
+ if(net1 == net2) return
+
+ //We assume net1 is larger. If net2 is in fact larger we are just going to make them switch places to reduce on code.
+ if(net1.cables.len < net2.cables.len) //net2 is larger than net1. Let's switch them around
+ var/temp = net1
+ net1 = net2
+ net2 = temp
+
+ for(var/i=1,i<=net2.nodes.len,i++) //merge net2 into net1
+ var/obj/machinery/power/Node = net2.nodes[i]
+ if(Node)
+ Node.powernet = net1
+ net1.nodes[Node] = Node
+
+ for(var/i=1,i<=net2.cables.len,i++)
+ var/obj/structure/cable/Cable = net2.cables[i]
+ if(Cable)
+ Cable.powernet = net1
+ net1.cables += Cable
+
+ del(net2)
+ return net1
\ No newline at end of file
diff --git a/code/modules/power/profiling.dm b/code/modules/power/profiling.dm
new file mode 100644
index 00000000..3cf8c3a0
--- /dev/null
+++ b/code/modules/power/profiling.dm
@@ -0,0 +1,71 @@
+datum
+
+var/global/enable_power_update_profiling = 0
+
+var/global/power_profiled_time = 0
+var/global/power_last_profile_time = 0
+var/global/list/power_update_requests_by_machine = list()
+var/global/list/power_update_requests_by_area = list()
+
+/proc/log_power_update_request(area/A, obj/machinery/M)
+ if (!enable_power_update_profiling)
+ return
+
+ var/machine_type = "[M.type]"
+ if (machine_type in power_update_requests_by_machine)
+ power_update_requests_by_machine[machine_type] += 1
+ else
+ power_update_requests_by_machine[machine_type] = 1
+
+ if (A.name in power_update_requests_by_area)
+ power_update_requests_by_area[A.name] += 1
+ else
+ power_update_requests_by_area[A.name] = 1
+
+ power_profiled_time += (world.time - power_last_profile_time)
+ power_last_profile_time = world.time
+
+/client/proc/toggle_power_update_profiling()
+ set name = "Toggle Area Power Update Profiling"
+ set desc = "Toggles the recording of area power update requests."
+ set category = "Debug"
+ if(!check_rights(R_DEBUG)) return
+
+ if(enable_power_update_profiling)
+ enable_power_update_profiling = 0
+
+ usr << "Area power update profiling disabled."
+ message_admins("[key_name(src)] toggled area power update profiling off.")
+ log_admin("[key_name(src)] toggled area power update profiling off.")
+ else
+ enable_power_update_profiling = 1
+ power_last_profile_time = world.time
+
+ usr << "Area power update profiling enabled."
+ message_admins("[key_name(src)] toggled area power update profiling on.")
+ log_admin("[key_name(src)] toggled area power update profiling on.")
+
+ feedback_add_details("admin_verb","APUP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
+/client/proc/view_power_update_stats_machines()
+ set name = "View Area Power Update Statistics By Machines"
+ set desc = "See which types of machines are triggering area power updates."
+ set category = "Debug"
+
+ if(!check_rights(R_DEBUG)) return
+
+ usr << "Total profiling time: [power_profiled_time] ticks"
+ for (var/M in power_update_requests_by_machine)
+ usr << "[M] = [power_update_requests_by_machine[M]]"
+
+/client/proc/view_power_update_stats_area()
+ set name = "View Area Power Update Statistics By Area"
+ set desc = "See which areas are having area power updates."
+ set category = "Debug"
+
+ if(!check_rights(R_DEBUG)) return
+
+ usr << "Total profiling time: [power_profiled_time] ticks"
+ usr << "Total profiling time: [power_profiled_time] ticks"
+ for (var/A in power_update_requests_by_area)
+ usr << "[A] = [power_update_requests_by_area[A]]"
\ No newline at end of file
diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm
index 7c0c4632..3f6e9416 100644
--- a/code/modules/power/singularity/collector.dm
+++ b/code/modules/power/singularity/collector.dm
@@ -13,6 +13,7 @@ var/global/list/rad_collectors = list()
// use_power = 0
var/obj/item/weapon/tank/plasma/P = null
var/last_power = 0
+ var/last_power_new = 0
var/active = 0
var/locked = 0
var/drainratio = 1
@@ -26,13 +27,17 @@ var/global/list/rad_collectors = list()
..()
/obj/machinery/power/rad_collector/process()
+ //so that we don't zero out the meter if the SM is processed first.
+ last_power = last_power_new
+ last_power_new = 0
+
+
if(P)
- if(P.air_contents.toxins <= 0)
+ if(P.air_contents.gas["toxins"] == 0)
investigate_log(" out of fuel.","singulo")
- P.air_contents.toxins = 0
eject()
else
- P.air_contents.adjust(tx = -0.001*drainratio)
+ P.air_contents.adjust_gas("toxins", -0.001*drainratio)
return
@@ -42,7 +47,7 @@ var/global/list/rad_collectors = list()
toggle_power()
user.visible_message("[user.name] turns the [src.name] [active? "on":"off"].", \
"You turn the [src.name] [active? "on":"off"].")
- investigate_log("turned [active?" on":" off"] by [user.key]. [P?"Fuel: [round(P.air_contents.toxins/0.29)]%":" It is empty"].","singulo")
+ investigate_log("turned [active?" on":" off"] by [user.key]. [P?"Fuel: [round(P.air_contents.gas["toxins"]/0.29)]%":" It is empty"].","singulo")
return
else
user << "\red The controls are locked!"
@@ -51,10 +56,7 @@ var/global/list/rad_collectors = list()
/obj/machinery/power/rad_collector/attackby(obj/item/W, mob/user)
- if(istype(W, /obj/item/device/analyzer))
- user << "\blue The [W.name] detects that [last_power]W were recently produced."
- return 1
- else if(istype(W, /obj/item/weapon/tank/plasma))
+ if(istype(W, /obj/item/weapon/tank/plasma))
if(!src.anchored)
user << "\red The [src] needs to be secured to the floor first."
return 1
@@ -97,6 +99,11 @@ var/global/list/rad_collectors = list()
..()
return 1
+/obj/machinery/power/rad_collector/examine()
+ ..()
+ if (get_dist(usr, src) <= 3)
+ usr << "The meter indicates that \the [src] is collecting [last_power] W."
+ return 1
/obj/machinery/power/rad_collector/ex_act(severity)
switch(severity)
@@ -121,9 +128,9 @@ var/global/list/rad_collectors = list()
/obj/machinery/power/rad_collector/proc/receive_pulse(var/pulse_strength)
if(P && active)
var/power_produced = 0
- power_produced = P.air_contents.toxins*pulse_strength*20
+ power_produced = P.air_contents.gas["toxins"]*pulse_strength*20
add_avail(power_produced)
- last_power = power_produced
+ last_power_new = power_produced
return
return
diff --git a/code/modules/power/singularity/containment_field.dm b/code/modules/power/singularity/containment_field.dm
index e6aa55f2..6cd2b1de 100644
--- a/code/modules/power/singularity/containment_field.dm
+++ b/code/modules/power/singularity/containment_field.dm
@@ -56,11 +56,7 @@
if(!FG1 || !FG2)
del(src)
return 0
- if(iscarbon(user))
- var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
- s.set_up(5, 1, user.loc)
- s.start()
-
+ if(isliving(user))
hasShocked = 1
var/shock_damage = min(rand(30,40),rand(30,40))
user.electrocute_act(shock_damage, src)
@@ -69,27 +65,8 @@
user.throw_at(target, 200, 4)
sleep(20)
+
hasShocked = 0
- return
-
- else if(issilicon(user))
- var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
- s.set_up(5, 1, user.loc)
- s.start()
-
- hasShocked = 1
- var/shock_damage = rand(15,30)
- user.take_overall_damage(0,shock_damage)
- user.visible_message("\red [user.name] was shocked by the [src.name]!", \
- "\red Energy pulse detected, system damaged!", \
- "\red You hear an electrical crack")
- if(prob(20))
- user.Stun(2)
-
- sleep(20)
- hasShocked = 0
- return
-
return
/obj/machinery/containment_field/proc/set_master(var/master1,var/master2)
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm
index 0a22877d..0bf96f02 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/singularity/emitter.dm
@@ -1,4 +1,4 @@
-//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:33
+#define EMITTER_DAMAGE_POWER_TRANSFER 450 //used to transfer power to containment field generators
/obj/machinery/power/emitter
name = "Emitter"
@@ -8,14 +8,17 @@
anchored = 0
density = 1
req_access = list(access_engine_equip)
+ var/id = null
- use_power = 0
- idle_power_usage = 10
- active_power_usage = 300
+ use_power = 0 //uses powernet power, not APC power
+ active_power_usage = 30000 //30 kW laser. I guess that means 30 kJ per shot.
var/active = 0
var/powered = 0
var/fire_delay = 100
+ var/max_burst_delay = 100
+ var/min_burst_delay = 20
+ var/burst_shots = 3
var/last_shot = 0
var/shot_number = 0
var/state = 0
@@ -52,9 +55,11 @@
else
icon_state = "emitter"
-
/obj/machinery/power/emitter/attack_hand(mob/user as mob)
src.add_fingerprint(user)
+ activate(user)
+
+/obj/machinery/power/emitter/proc/activate(mob/user as mob)
if(state == 2)
if(!powernet)
user << "The emitter isn't connected to a wire."
@@ -104,8 +109,7 @@
return
if(((src.last_shot + src.fire_delay) <= world.time) && (src.active == 1))
- if(!active_power_usage || avail(active_power_usage))
- add_load(active_power_usage)
+ if(surplus() >= active_power_usage && add_load(active_power_usage) >= active_power_usage) //does the laser have enough power to shoot?
if(!powered)
powered = 1
update_icon()
@@ -118,13 +122,19 @@
return
src.last_shot = world.time
- if(src.shot_number < 3)
+ if(src.shot_number < burst_shots)
src.fire_delay = 2
src.shot_number ++
else
- src.fire_delay = rand(20,100)
+ src.fire_delay = rand(min_burst_delay, max_burst_delay)
src.shot_number = 0
+
+ //need to calculate the power per shot as the emitter doesn't fire continuously.
+ var/burst_time = (min_burst_delay + max_burst_delay)/2 + 2*(burst_shots-1)
+ var/power_per_shot = active_power_usage * (burst_time/10) / burst_shots
var/obj/item/projectile/beam/emitter/A = new /obj/item/projectile/beam/emitter( src.loc )
+ A.damage = round(power_per_shot/EMITTER_DAMAGE_POWER_TRANSFER)
+
playsound(src.loc, 'sound/weapons/emitter.ogg', 25, 1)
if(prob(35))
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm
index b4289730..96740dd7 100644
--- a/code/modules/power/singularity/field_generator.dm
+++ b/code/modules/power/singularity/field_generator.dm
@@ -12,7 +12,7 @@ field_generator power level display
-Aygar
*/
-#define field_generator_max_power 250
+#define field_generator_max_power 250000
/obj/machinery/field_generator
name = "Field Generator"
desc = "A large thermal battery that projects a high amount of energy when powered."
@@ -25,12 +25,16 @@ field_generator power level display
var/Varedit_start = 0
var/Varpower = 0
var/active = 0
- var/power = 20 // Current amount of power
+ var/power = 30000 // Current amount of power
var/state = 0
var/warming_up = 0
var/list/obj/machinery/containment_field/fields
var/list/obj/machinery/field_generator/connected_gens
var/clean_up = 0
+
+ //If keeping field generators powered is hard then increase the emitter active power usage.
+ var/gen_power_draw = 5500 //power needed per generator
+ var/field_power_draw = 2000 //power needed per field object
/obj/machinery/field_generator/update_icon()
@@ -167,8 +171,8 @@ field_generator power level display
return 0
/obj/machinery/field_generator/bullet_act(var/obj/item/projectile/Proj)
- if(Proj.flag != "bullet")
- power += Proj.damage
+ if(istype(Proj, /obj/item/projectile/beam))
+ power += Proj.damage * EMITTER_DAMAGE_POWER_TRANSFER
update_icon()
return 0
@@ -206,51 +210,44 @@ field_generator power level display
if(src.power > field_generator_max_power)
src.power = field_generator_max_power
- var/power_draw = 2
+ var/power_draw = gen_power_draw
+ for(var/obj/machinery/field_generator/FG in connected_gens)
+ if (!isnull(FG))
+ power_draw += gen_power_draw
for (var/obj/machinery/containment_field/F in fields)
- if (isnull(F))
- continue
- power_draw++
- if(draw_power(round(power_draw/2,1)))
+ if (!isnull(F))
+ power_draw += field_power_draw
+ power_draw /= 2 //because this will be mirrored for both generators
+ if(draw_power(round(power_draw)) >= power_draw)
return 1
else
for(var/mob/M in viewers(src))
- M.show_message("\red The [src.name] shuts down!")
+ M.show_message("\red \The [src] shuts down!")
turn_off()
investigate_log("ran out of power and deactivated","singulo")
src.power = 0
return 0
-//This could likely be better, it tends to start loopin if you have a complex generator loop setup. Still works well enough to run the engine fields will likely recode the field gens and fields sometime -Mport
-/obj/machinery/field_generator/proc/draw_power(var/draw = 0, var/failsafe = 0, var/obj/machinery/field_generator/G = null, var/obj/machinery/field_generator/last = null)
- if(Varpower)
- return 1
- if((G && G == src) || (failsafe >= 8))//Loopin, set fail
- return 0
- else
- failsafe++
+//Tries to draw the needed power from our own power reserve, or connected generators if we can. Returns the amount of power we were able to get.
+/obj/machinery/field_generator/proc/draw_power(var/draw = 0, var/list/flood_list = list())
+ flood_list += src
+
if(src.power >= draw)//We have enough power
src.power -= draw
- return 1
- else//Need more power
- draw -= src.power
- src.power = 0
- for(var/obj/machinery/field_generator/FG in connected_gens)
- if(isnull(FG))
- continue
- if(FG == last)//We just asked you
- continue
- if(G)//Another gen is askin for power and we dont have it
- if(FG.draw_power(draw,failsafe,G,src))//Can you take the load
- return 1
- else
- return 0
- else//We are askin another for power
- if(FG.draw_power(draw,failsafe,src,src))
- return 1
- else
- return 0
-
+ return draw
+
+ //Need more power
+ var/actual_draw = src.power //already checked that power < draw
+ src.power = 0
+
+ for(var/obj/machinery/field_generator/FG in connected_gens)
+ if (FG in flood_list)
+ continue
+ actual_draw += FG.draw_power(draw - actual_draw, flood_list) //since the flood list reference is shared this actually works.
+ if (actual_draw >= draw)
+ return actual_draw
+
+ return actual_draw
/obj/machinery/field_generator/proc/start_fields()
if(!src.state == 2 || !anchored)
diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
index 783cdcfc..60ad2c15 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
@@ -6,7 +6,8 @@ proc
emit_particle()
1 power box
-the only part of this thing that uses power, can hack to mess with the pa/make it better
+the only part of this thing that uses power, can hack to mess with the pa/make it better.
+Lies, only the control computer draws power.
1 fuel chamber
contains procs for mixing gas and whatever other fuel it uses
@@ -71,7 +72,6 @@ So, hopefully this is helpful if any more icons are to be added/changed/wonderin
var/strength = null
var/desc_holder = null
-
/obj/structure/particle_accelerator/end_cap
name = "Alpha Particle Generation Array"
desc_holder = "This is where Alpha particles are generated from \[REDACTED\]"
@@ -138,33 +138,29 @@ So, hopefully this is helpful if any more icons are to be added/changed/wonderin
/obj/structure/particle_accelerator/ex_act(severity)
switch(severity)
if(1.0)
- Del(src)
+ del(src)
return
if(2.0)
if (prob(50))
- Del(src)
+ del(src)
return
if(3.0)
if (prob(25))
- Del(src)
+ del(src)
return
else
return
-/obj/structure/particle_accelerator/Del()
- if(master && master.connected_parts)
- master.connected_parts.Remove(src)
- ..()
/obj/structure/particle_accelerator/blob_act()
if(prob(50))
- Del()
+ del(src)
return
/obj/structure/particle_accelerator/meteorhit()
if(prob(50))
- Del()
+ del(src)
return
/obj/structure/particle_accelerator/update_icon()
diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/singularity/particle_accelerator/particle_control.dm
index cdc4013d..b0eb96e1 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_control.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm
@@ -10,7 +10,7 @@
density = 1
use_power = 0
idle_power_usage = 500
- active_power_usage = 10000
+ active_power_usage = 70000 //70 kW per unit of strength
construction_state = 0
active = 0
dir = 1
@@ -20,6 +20,7 @@
/obj/machinery/particle_accelerator/control_box/New()
connected_parts = list()
+ active_power_usage = initial(active_power_usage) * (strength + 1)
..()
@@ -29,7 +30,7 @@
/obj/machinery/particle_accelerator/control_box/update_state()
if(construction_state < 3)
- use_power = 0
+ update_use_power(0)
assembled = 0
active = 0
for(var/obj/structure/particle_accelerator/part in connected_parts)
@@ -39,18 +40,12 @@
connected_parts = list()
return
if(!part_scan())
- use_power = 1
+ update_use_power(1)
active = 0
connected_parts = list()
return
-/obj/machinery/particle_accelerator/control_box/Del()
- if(active)
- toggle_power()
- process()
- ..()
-
/obj/machinery/particle_accelerator/control_box/update_icon()
if(active)
icon_state = "[reference]p1"
@@ -93,6 +88,7 @@
else if(href_list["scan"])
src.part_scan()
else if(href_list["strengthup"])
+ var/old_strength = strength
strength++
if(strength > 2)
strength = 2
@@ -104,8 +100,13 @@
for(var/obj/structure/particle_accelerator/part in connected_parts)
part.strength = strength
part.update_icon()
+
+ if (strength != old_strength)
+ active_power_usage = initial(active_power_usage) * (strength + 1)
+ use_power(0) //update power usage
else if(href_list["strengthdown"])
+ var/old_strength = strength
strength--
if(strength < 0)
strength = 0
@@ -117,6 +118,10 @@
for(var/obj/structure/particle_accelerator/part in connected_parts)
part.strength = strength
part.update_icon()
+
+ if (strength != old_strength)
+ active_power_usage = initial(active_power_usage) * (strength + 1)
+ use_power(0) //update power usage
src.updateDialog()
src.update_icon()
return
@@ -126,9 +131,9 @@
..()
if(stat & NOPOWER)
active = 0
- use_power = 0
+ update_use_power(0)
else if(!stat && construction_state == 3)
- use_power = 1
+ update_use_power(1)
return
@@ -191,7 +196,6 @@
if(PA.connect_master(src))
if(PA.report_ready(src))
src.connected_parts.Add(PA)
- PA.master = src
return 1
return 0
@@ -199,13 +203,13 @@
/obj/machinery/particle_accelerator/control_box/proc/toggle_power()
src.active = !src.active
if(src.active)
- src.use_power = 2
+ update_use_power(2)
for(var/obj/structure/particle_accelerator/part in connected_parts)
part.strength = src.strength
part.powered = 1
part.update_icon()
else
- src.use_power = 1
+ update_use_power(1)
for(var/obj/structure/particle_accelerator/part in connected_parts)
part.strength = null
part.powered = 0
@@ -225,7 +229,7 @@
dat += "Particle Accelerator Control Panel "
dat += " Close"
dat += "Status: "
- if(!assembled || connected_parts.len < 6)
+ if(!assembled)
dat += "Unable to detect all parts! "
dat += " Run Scan"
else
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index 010bcd61..89ac5091 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -481,8 +481,9 @@ var/global/list/uneatable = list(
/obj/machinery/singularity/narsie/large/New()
..()
world << " NAR-SIE HAS RISEN"
- if(emergency_shuttle)
- emergency_shuttle.incall(0.5) // Cannot recall
+ if(emergency_shuttle && emergency_shuttle.can_call())
+ emergency_shuttle.call_evac()
+ emergency_shuttle.launch_time = 0 // Cannot recall
/obj/machinery/singularity/narsie/process()
eat()
diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm
index aded3dfa..512b9429 100644
--- a/code/modules/power/smes.dm
+++ b/code/modules/power/smes.dm
@@ -1,8 +1,8 @@
// the SMES
// stores power
-#define SMESMAXCHARGELEVEL 200000
-#define SMESMAXOUTPUT 200000
+#define SMESMAXCHARGELEVEL 250000
+#define SMESMAXOUTPUT 250000
/obj/machinery/power/smes
name = "power storage unit"
@@ -11,26 +11,33 @@
density = 1
anchored = 1
use_power = 0
- var/output = 50000
- var/lastout = 0
- var/loaddemand = 0
- var/capacity = 5e6
- var/charge = 1e6
- var/charging = 0
- var/chargemode = 0
- var/chargecount = 0
- var/chargelevel = 50000
- var/online = 1
+ var/output = 50000 //Amount of power it tries to output
+ var/lastout = 0 //Amount of power it actually outputs to the powernet
+ var/loaddemand = 0 //For use in restore()
+ var/capacity = 5e6 //Maximum amount of power it can hold
+ var/charge = 1.0e6 //Current amount of power it holds
+ var/charging = 0 //1 if it's actually charging, 0 if not
+ var/chargemode = 0 //1 if it's trying to charge, 0 if not.
+ //var/chargecount = 0
+ var/chargelevel = 50000 //Amount of power it tries to charge from powernet
+ var/online = 1 //1 if it's outputting power, 0 if not.
var/name_tag = null
var/obj/machinery/power/terminal/terminal = null
//Holders for powerout event.
var/last_output = 0
var/last_charge = 0
var/last_online = 0
+ var/open_hatch = 0
+ var/building_terminal = 0 //Suggestions about how to avoid clickspam building several terminals accepted!
+ var/input_level_max = SMESMAXCHARGELEVEL
+ var/output_level_max = SMESMAXOUTPUT
/obj/machinery/power/smes/New()
..()
spawn(5)
+ if(!powernet)
+ connect_to_network()
+
dir_loop:
for(var/d in cardinal)
var/turf/T = get_step(src, d)
@@ -42,6 +49,8 @@
stat |= BROKEN
return
terminal.master = src
+ if(!terminal.powernet)
+ terminal.connect_to_network()
updateicon()
return
@@ -71,7 +80,6 @@
/obj/machinery/power/smes/process()
-
if(stat & BROKEN) return
//store machine state to see if we need to update the icon overlays
@@ -80,41 +88,23 @@
var/last_onln = online
if(terminal)
- var/excess = terminal.surplus()
+ //If chargemod is set, try to charge
+ //Use charging to let the player know whether we were able to obtain our target load.
+ //TODO: Add a meter to tell players how much charge we are actually getting, and only set charging to 0 when we are unable to get any charge at all.
+ if(chargemode)
+ var/target_load = min((capacity-charge)/SMESRATE, chargelevel) // charge at set rate, limited to spare capacity
+ var/actual_load = add_load(target_load) // add the load to the terminal side network
+ charge += actual_load * SMESRATE // increase the charge
- if(charging)
- if(excess >= 0) // if there's power available, try to charge
-
- var/load = min((capacity-charge)/SMESRATE, chargelevel) // charge at set rate, limited to spare capacity
-
- charge += load * SMESRATE // increase the charge
-
- add_load(load) // add the load to the terminal side network
-
- else // if not enough capcity
- charging = 0 // stop charging
- chargecount = 0
-
- else
- if(chargemode)
- if(chargecount > rand(3,6))
- charging = 1
- chargecount = 0
-
- if(excess > chargelevel)
- chargecount++
- else
- chargecount = 0
+ if (actual_load >= target_load) // did the powernet have enough power available for us?
+ charging = 1
else
- chargecount = 0
+ charging = 0
if(online) // if outputting
lastout = min( charge/SMESRATE, output) //limit output to that stored
-
charge -= lastout*SMESRATE // reduce the storage (may be recovered in /restore() if excessive)
-
add_avail(lastout) // add output to powernet (smes side)
-
if(charge < 0.0001)
online = 0 // stop output if charge falls to zero
@@ -155,10 +145,40 @@
updateicon()
return
+//Will return 1 on failure
+/obj/machinery/power/smes/proc/make_terminal(const/mob/user)
+ if (user.loc == loc)
+ user << " You must not be on the same tile as the [src]."
+ return 1
+
+ //Direction the terminal will face to
+ var/tempDir = get_dir(user, src)
+ switch(tempDir)
+ if (NORTHEAST, SOUTHEAST)
+ tempDir = EAST
+ if (NORTHWEST, SOUTHWEST)
+ tempDir = WEST
+ var/turf/tempLoc = get_step(src, reverse_direction(tempDir))
+ if (istype(tempLoc, /turf/space))
+ user << " You can't build a terminal on space."
+ return 1
+ else if (istype(tempLoc))
+ if(tempLoc.intact)
+ user << " You must remove the floor plating first."
+ return 1
+ user << " You start adding cable to the [src]."
+ if(do_after(user, 50))
+ terminal = new /obj/machinery/power/terminal(tempLoc)
+ terminal.dir = tempDir
+ terminal.master = src
+ return 0
+ return 1
+
/obj/machinery/power/smes/add_load(var/amount)
if(terminal && terminal.powernet)
- terminal.powernet.newload += amount
+ return terminal.powernet.draw_power(amount)
+ return 0
/obj/machinery/power/smes/attack_ai(mob/user)
@@ -169,11 +189,62 @@
/obj/machinery/power/smes/attack_hand(mob/user)
add_fingerprint(user)
ui_interact(user)
-
-
-/obj/machinery/power/smes/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null)
- if(stat & BROKEN)
+
+/obj/machinery/power/smes/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob)
+ if(istype(W, /obj/item/weapon/screwdriver))
+ if(!open_hatch)
+ open_hatch = 1
+ user << " You open the maintenance hatch of [src]."
+ else
+ open_hatch = 0
+ user << " You close the maintenance hatch of [src]."
+ if (open_hatch)
+ if(istype(W, /obj/item/weapon/cable_coil) && !terminal && !building_terminal)
+ building_terminal = 1
+ var/obj/item/weapon/cable_coil/CC = W
+ if (CC.amount < 10)
+ user << " You need more cables."
+ building_terminal = 0
+ return
+ if (make_terminal(user))
+ building_terminal = 0
+ return
+ building_terminal = 0
+ CC.use(10)
+ user.visible_message(\
+ " [user.name] has added cables to the [src].",\
+ " You added cables to the [src].")
+ terminal.connect_to_network()
+ stat = 0
+
+ else if(istype(W, /obj/item/weapon/wirecutters) && terminal && !building_terminal)
+ building_terminal = 1
+ var/turf/tempTDir = terminal.loc
+ if (istype(tempTDir))
+ if(tempTDir.intact)
+ user << " You must remove the floor plating first."
+ else
+ user << " You begin to cut the cables..."
+ playsound(get_turf(src), 'sound/items/Deconstruct.ogg', 50, 1)
+ if(do_after(user, 50))
+ if (prob(50) && electrocute_mob(usr, terminal.powernet, terminal))
+ var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ s.set_up(5, 1, src)
+ s.start()
+ building_terminal = 0
+ return
+ new /obj/item/weapon/cable_coil(loc,10)
+ user.visible_message(\
+ " [user.name] cut the cables and dismantled the power terminal.",\
+ " You cut the cables and dismantle the power terminal.")
+ del(terminal)
+ building_terminal = 0
+
+
+/obj/machinery/power/smes/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+
+ if(stat & BROKEN)
return
// this is the data which will be sent to the ui
@@ -183,25 +254,25 @@
data["charging"] = charging
data["chargeMode"] = chargemode
data["chargeLevel"] = chargelevel
- data["chargeMax"] = SMESMAXCHARGELEVEL
+ data["chargeMax"] = input_level_max
data["outputOnline"] = online
data["outputLevel"] = output
- data["outputMax"] = SMESMAXOUTPUT
+ data["outputMax"] = output_level_max
data["outputLoad"] = round(loaddemand)
// update the ui if it exists, returns null if no ui is passed/found
- ui = nanomanager.try_update_ui(user, src, ui_key, ui, data)
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
// the ui does not exist, so we'll create a new() one
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
ui = new(user, src, ui_key, "smes.tmpl", "SMES Power Storage Unit", 540, 380)
// when the ui is first opened this is the data it will use
- ui.set_initial_data(data)
+ ui.set_initial_data(data)
// open the new ui window
ui.open()
// auto update every Master Controller tick
ui.set_auto_update(1)
-
+
/obj/machinery/power/smes/Topic(href, href_list)
..()
@@ -236,30 +307,29 @@
if("min")
chargelevel = 0
if("max")
- chargelevel = SMESMAXCHARGELEVEL //30000
+ chargelevel = input_level_max
if("set")
- chargelevel = input(usr, "Enter new input level (0-[SMESMAXCHARGELEVEL])", "SMES Input Power Control", chargelevel) as num
- chargelevel = max(0, min(SMESMAXCHARGELEVEL, chargelevel)) // clamp to range
+ chargelevel = input(usr, "Enter new input level (0-[input_level_max])", "SMES Input Power Control", chargelevel) as num
+ chargelevel = max(0, min(input_level_max, chargelevel)) // clamp to range
else if( href_list["output"] )
switch( href_list["output"] )
if("min")
output = 0
if("max")
- output = SMESMAXOUTPUT //30000
+ output = output_level_max
if("set")
- output = input(usr, "Enter new output level (0-[SMESMAXOUTPUT])", "SMES Output Power Control", output) as num
- output = max(0, min(SMESMAXOUTPUT, output)) // clamp to range
+ output = input(usr, "Enter new output level (0-[output_level_max])", "SMES Output Power Control", output) as num
+ output = max(0, min(output_level_max, output)) // clamp to range
investigate_log("input/output; [chargelevel>output?" ":""][chargelevel]/[output] | Output-mode: [online?"on":"off"] | Input-mode: [chargemode?"auto":"off"] by [usr.key]","singulo")
-
+
return 1
-
+
/obj/machinery/power/smes/proc/ion_act()
if(src.z == 1)
if(prob(1)) //explosion
- world << "\red SMES explosion in [src.loc.loc]"
for(var/mob/M in viewers(src))
M.show_message("\red The [src.name] is making strange noises!", 3, "\red You hear sizzling electronics.", 2)
sleep(10*pick(4,5,6,7,10,14))
@@ -267,11 +337,10 @@
smoke.set_up(3, 0, src.loc)
smoke.attach(src)
smoke.start()
- explosion(src.loc, -1, 0, 1, 3, 0)
+ explosion(src.loc, -1, 0, 1, 3, 1, 0)
del(src)
return
if(prob(15)) //Power drain
- world << "\red SMES power drain in [src.loc.loc]"
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
s.set_up(3, 1, src)
s.start()
@@ -280,7 +349,6 @@
else
emp_act(2)
if(prob(5)) //smoke only
- world << "\red SMES smoke in [src.loc.loc]"
var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread()
smoke.set_up(3, 0, src.loc)
smoke.attach(src)
@@ -305,12 +373,12 @@
/obj/machinery/power/smes/magical
name = "magical power storage unit"
desc = "A high-capacity superconducting magnetic energy storage (SMES) unit. Magically produces power."
- process()
- capacity = INFINITY
- charge = INFINITY
- ..()
-
+ output = SMESMAXOUTPUT
+/obj/machinery/power/smes/magical/process()
+ capacity = INFINITY
+ charge = INFINITY
+ ..()
/proc/rate_control(var/S, var/V, var/C, var/Min=1, var/Max=5, var/Limit=null)
var/href = " 500)
- add_load(500)
+ if(surplus() >= power_usage && add_load(power_usage) >= power_usage)
stat &= ~NOPOWER
else
stat |= NOPOWER
diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm
index 466c6bdd..6464bb6f 100644
--- a/code/modules/power/turbine.dm
+++ b/code/modules/power/turbine.dm
@@ -51,6 +51,9 @@
turbine = locate() in get_step(src, get_dir(inturf, src))
if(!turbine)
stat |= BROKEN
+ else
+ turbine.stat &= !BROKEN
+ turbine.compressor = src
#define COMPFRICTION 5e5
@@ -67,7 +70,7 @@
return
rpm = 0.9* rpm + 0.1 * rpmtarget
var/datum/gas_mixture/environment = inturf.return_air()
- var/transfer_moles = environment.total_moles()/10
+ var/transfer_moles = environment.total_moles / 10
//var/transfer_moles = rpm/10000*capacity
var/datum/gas_mixture/removed = inturf.remove_air(transfer_moles)
gas_contained.merge(removed)
@@ -105,6 +108,9 @@
compressor = locate() in get_step(src, get_dir(outturf, src))
if(!compressor)
stat |= BROKEN
+ else
+ compressor.stat &= !BROKEN
+ compressor.turbine = src
#define TURBPRES 9000000
@@ -123,14 +129,14 @@
lastgen = ((compressor.rpm / TURBGENQ)**TURBGENG) *TURBGENQ
add_avail(lastgen)
- var/newrpm = ((compressor.gas_contained.temperature) * compressor.gas_contained.total_moles())/4
+ var/newrpm = ((compressor.gas_contained.temperature) * compressor.gas_contained.total_moles)/4
newrpm = max(0, newrpm)
if(!compressor.starter || newrpm > 1000)
compressor.rpmtarget = newrpm
- if(compressor.gas_contained.total_moles()>0)
- var/oamount = min(compressor.gas_contained.total_moles(), (compressor.rpm+100)/35000*compressor.capacity)
+ if(compressor.gas_contained.total_moles>0)
+ var/oamount = min(compressor.gas_contained.total_moles, (compressor.rpm+100)/35000*compressor.capacity)
var/datum/gas_mixture/removed = compressor.gas_contained.remove(oamount)
outturf.assume_air(removed)
diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm
index 64c0952f..bd5b4df3 100644
--- a/code/modules/projectiles/ammunition.dm
+++ b/code/modules/projectiles/ammunition.dm
@@ -51,7 +51,7 @@
flags = FPRINT | TABLEPASS | CONDUCT
slot_flags = SLOT_BELT
item_state = "syringe_kit"
- m_amt = 50000
+ matter = list("metal" = 50000)
throwforce = 2
w_class = 2.0
throw_speed = 4
diff --git a/code/modules/projectiles/ammunition/boxes.dm b/code/modules/projectiles/ammunition/boxes.dm
index 5f7b38d0..956eb79d 100644
--- a/code/modules/projectiles/ammunition/boxes.dm
+++ b/code/modules/projectiles/ammunition/boxes.dm
@@ -166,7 +166,7 @@
origin_tech = "combat=2"
ammo_type = "/obj/item/ammo_casing/shotgun"
max_ammo = 8
- m_amt = 100000
+ matter = list("metal" = 100000)
/* no buckshots. never again. too hard to steal forever.
/obj/item/ammo_magazine/shotgun/buck
name = "Ammunition Box (buckshot)"
@@ -177,17 +177,17 @@
name = "Ammunition Box (stun shells)"
ammo_type = "/obj/item/ammo_casing/shotgun/stunshell"
max_ammo = 8
- m_amt = 20000
+ matter = list("metal" = 20000)
/obj/item/ammo_magazine/shotgun/beanbag
name = "Ammunition Box (beanbag shells)"
ammo_type = "/obj/item/ammo_casing/shotgun/beanbag"
max_ammo = 8
- m_amt = 4000
+ matter = list("metal" = 4000)
/obj/item/ammo_magazine/shotgun/incendiary
name = "Ammunition Box (incendiary shells)"
ammo_type = "/obj/item/ammo_casing/shotgun/incendiary"
max_ammo = 8
- m_amt = 100000
+ matter = list("metal" = 100000)
diff --git a/code/modules/projectiles/ammunition/bullets.dm b/code/modules/projectiles/ammunition/bullets.dm
index 3ce29735..808d87d5 100644
--- a/code/modules/projectiles/ammunition/bullets.dm
+++ b/code/modules/projectiles/ammunition/bullets.dm
@@ -65,14 +65,13 @@
icon_state = "gshell"
caliber = "shotgun"
projectile_type = "/obj/item/projectile/bullet"
- m_amt = 12500
+ matter = list("metal" = 12500)
/obj/item/ammo_casing/shotgun/incendiary
name = "shotgun shell"
desc = "A 12 gauge shell."
icon_state = "ishell"
projectile_type = "/obj/item/projectile/bullet/incendiary/shell"
- m_amt = 12500
/obj/item/ammo_casing/shotgun/blank
@@ -80,7 +79,7 @@
desc = "A blank shell."
icon_state = "blshell"
projectile_type = ""
- m_amt = 250
+ matter = list("metal" = 250)
/obj/item/ammo_casing/shotgun/beanbag
@@ -88,7 +87,7 @@
desc = "A weak beanbag shell."
icon_state = "bshell"
projectile_type = "/obj/item/projectile/bullet/weakbullet/beanbag"
- m_amt = 500
+ matter = list("metal" = 500)
/obj/item/ammo_casing/shotgun/stunshell
@@ -96,7 +95,7 @@
desc = "A stunning shell."
icon_state = "stunshell"
projectile_type = "/obj/item/projectile/bullet/stunshot"
- m_amt = 2500
+ matter = list("metal" = 2500)
/obj/item/ammo_casing/shotgun/dart
@@ -104,7 +103,7 @@
desc = "A dart for use in shotguns."
icon_state = "dart"
projectile_type = "/obj/item/projectile/energy/dart"
- m_amt = 12500
+ matter = list("metal" = 12500)
/obj/item/ammo_casing/a762
desc = "A 7.62 bullet casing."
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index f9bee693..ee846224 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -6,7 +6,7 @@
item_state = "gun"
flags = FPRINT | TABLEPASS | CONDUCT
slot_flags = SLOT_BELT
- m_amt = 2000
+ matter = list("metal" = 2000)
w_class = 3.0
throwforce = 5
throw_speed = 4
diff --git a/code/modules/projectiles/guns/energy/erifles.dm b/code/modules/projectiles/guns/energy/erifles.dm
index e0899974..96eadadf 100644
--- a/code/modules/projectiles/guns/energy/erifles.dm
+++ b/code/modules/projectiles/guns/energy/erifles.dm
@@ -58,7 +58,7 @@
fire_sound = 'sound/weapons/Laser.ogg'
slot_flags = SLOT_BACK
w_class = 4
- m_amt = 2000
+ matter = list("metal" = 2000)
charge_cost = 50 //odd numbers due to a requirement to have 20 shots. Easiest way.
origin_tech = "combat=3;magnets=2"
projectile_type = "/obj/item/projectile/beam"
diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm
index b07e64dd..60e5702c 100644
--- a/code/modules/projectiles/guns/energy/laser.dm
+++ b/code/modules/projectiles/guns/energy/laser.dm
@@ -6,7 +6,7 @@
fire_sound = 'sound/weapons/Laser.ogg'
slot_flags = SLOT_BACK
w_class = 4
- m_amt = 2000
+ matter = list("metal" = 2000)
origin_tech = "combat=3;magnets=2"
projectile_type = "/obj/item/projectile/beam"
diff --git a/code/modules/projectiles/guns/energy/modular.dm b/code/modules/projectiles/guns/energy/modular.dm
index 7b218704..87564873 100644
--- a/code/modules/projectiles/guns/energy/modular.dm
+++ b/code/modules/projectiles/guns/energy/modular.dm
@@ -6,7 +6,7 @@
fire_sound = 'sound/weapons/Laser.ogg'
slot_flags = SLOT_BACK
w_class = 4
- m_amt = 2000
+ matter = list("metal" = 2000)
origin_tech = "combat=3;magnets=2"
projectile_type = "/obj/item/projectile/beam/reallyweak"
fire_delay = 16
diff --git a/code/modules/projectiles/guns/energy/stun.dm b/code/modules/projectiles/guns/energy/stun.dm
index 7d612812..c355d3b2 100644
--- a/code/modules/projectiles/guns/energy/stun.dm
+++ b/code/modules/projectiles/guns/energy/stun.dm
@@ -69,7 +69,7 @@
icon_state = "crossbow"
w_class = 2.0
item_state = "crossbow"
- m_amt = 2000
+ matter = list("metal" = 2000)
origin_tech = "combat=2;magnets=2;syndicate=5"
silenced = 1
fire_sound = 'sound/weapons/Genhit.ogg'
@@ -108,7 +108,7 @@
desc = "A weapon favored by syndicate infiltration teams."
w_class = 4.0
force = 10
- m_amt = 200000
+ matter = list("metal" = 200000)
projectile_type = "/obj/item/projectile/energy/bolt/large"
diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm
index 6d1bdf99..2ce69211 100644
--- a/code/modules/projectiles/guns/projectile.dm
+++ b/code/modules/projectiles/guns/projectile.dm
@@ -9,7 +9,7 @@
caliber = "357"
origin_tech = "combat=2;materials=2"
w_class = 3.0
- m_amt = 1000
+ matter = list("metal" = 1000)
recoil = 1
var/ammo_type = "/obj/item/ammo_casing/a357"
var/list/loaded = list()
diff --git a/code/modules/reagents/Chemistry-Colours.dm b/code/modules/reagents/Chemistry-Colours.dm
index 77a833bb..d9da7d14 100644
--- a/code/modules/reagents/Chemistry-Colours.dm
+++ b/code/modules/reagents/Chemistry-Colours.dm
@@ -34,7 +34,7 @@
var/blue = mixOneColor(weight,bluecolor)
//assemble all the pieces
- var/finalcolor = "#[red][green][blue]"
+ var/finalcolor = rgb(red, green, blue)
return finalcolor
/proc/mixOneColor(var/list/weight, var/list/color)
@@ -58,10 +58,9 @@
mixedcolor = round(mixedcolor)
//until someone writes a formal proof for this algorithm, let's keep this in
- if(mixedcolor<0x00 || mixedcolor>0xFF)
- return 0
+// if(mixedcolor<0x00 || mixedcolor>0xFF)
+// return 0
+ //that's not the kind of operation we are running here, nerd
+ mixedcolor=min(max(mixedcolor,0),255)
- var/finalcolor = num2hex(mixedcolor)
- while(length(finalcolor)<2)
- finalcolor = text("0[]",finalcolor) //Takes care of leading zeroes
- return finalcolor
+ return mixedcolor
diff --git a/code/modules/reagents/Chemistry-Machinery.dm b/code/modules/reagents/Chemistry-Machinery.dm
index b43dc1f7..381ce6fb 100644
--- a/code/modules/reagents/Chemistry-Machinery.dm
+++ b/code/modules/reagents/Chemistry-Machinery.dm
@@ -14,16 +14,13 @@
var/energy = 100
var/max_energy = 100
var/amount = 30
- var/accept_glass = 0
- var/beaker = null
+ var/accept_glass = 0 //At 0 ONLY accepts glass containers. Kinda misleading varname.
+ var/atom/beaker = null
var/recharged = 0
var/hackedcheck = 0
var/list/dispensable_reagents = list("hydrogen","lithium","carbon","nitrogen","oxygen","fluorine",
"sodium","aluminum","silicon","phosphorus","sulfur","chlorine","potassium","iron",
"copper","mercury","radium","water","ethanol","sugar","sacid","tungsten")
- var/list/broken_requirements = list()
- var/broken_on_spawn = 0
- moveable = 1
/obj/machinery/chem_dispenser/proc/recharge()
if(stat & (BROKEN|NOPOWER)) return
@@ -35,15 +32,10 @@
nanomanager.update_uis(src) // update all UIs attached to src
/obj/machinery/chem_dispenser/power_change()
- if(powered())
- stat &= ~NOPOWER
- else
- spawn(rand(0, 15))
- stat |= NOPOWER
+ ..()
nanomanager.update_uis(src) // update all UIs attached to src
/obj/machinery/chem_dispenser/process()
-
if(recharged <= 0)
recharge()
recharged = 15
@@ -55,27 +47,6 @@
recharge()
dispensable_reagents = sortList(dispensable_reagents)
- if(broken_on_spawn)
- var/amount = pick(1,2,2,3,4)
- var/list/options = list()
- options[/obj/item/weapon/stock_parts/capacitor/adv] = "Add an advanced capacitor to fix it."
- options[/obj/item/weapon/stock_parts/console_screen] = "Replace the console screen to fix it."
- options[/obj/item/weapon/stock_parts/manipulator/pico] = "Upgrade to a pico manipulator to fix it."
- options[/obj/item/weapon/stock_parts/matter_bin/adv] = "Give it an advanced matter bin to fix it."
- options[/obj/item/stack/sheet/mineral/diamond] = "Line up a cut diamond with the nozzle to fix it."
- options[/obj/item/stack/sheet/mineral/uranium] = "Position a uranium sheet inside to fix it."
- options[/obj/item/stack/sheet/mineral/plasma] = "Enter a block of plasma to fix it."
- options[/obj/item/stack/sheet/mineral/silver] = "Cover the internals with a silver lining to fix it."
- options[/obj/item/stack/sheet/mineral/gold] = "Wire a golden filament to fix it."
- options[/obj/item/stack/sheet/plasteel] = "Surround the outside with a plasteel cover to fix it."
- options[/obj/item/stack/sheet/rglass] = "Insert a pane of reinforced glass to fix it."
- stat |= BROKEN
- while(amount > 0)
- amount -= 1
-
- var/index = pick(options)
- broken_requirements[index] = options[index]
- options -= index
/obj/machinery/chem_dispenser/ex_act(severity)
switch(severity)
@@ -105,18 +76,15 @@
*
* @return nothing
*/
-/obj/machinery/chem_dispenser/ui_interact(mob/user, ui_key = "main",var/datum/nanoui/ui = null)
- if(broken_requirements.len)
- user << "[src] is broken. [broken_requirements[broken_requirements[1]]]"
- return
+/obj/machinery/chem_dispenser/ui_interact(mob/user, ui_key = "main",var/datum/nanoui/ui = null, var/force_open = 1)
if(stat & (BROKEN|NOPOWER)) return
if(user.stat || user.restrained()) return
// this is the data which will be sent to the ui
var/data[0]
data["amount"] = amount
- data["energy"] = energy
- data["maxEnergy"] = max_energy
+ data["energy"] = round(energy)
+ data["maxEnergy"] = round(max_energy)
data["isBeakerLoaded"] = beaker ? 1 : 0
data["glass"] = accept_glass
var beakerContents[0]
@@ -142,11 +110,11 @@
data["chemicals"] = chemicals
// update the ui if it exists, returns null if no ui is passed/found
- ui = nanomanager.try_update_ui(user, src, ui_key, ui, data)
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
// the ui does not exist, so we'll create a new() one
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "chem_dispenser.tmpl", ui_title, 380, 650)
+ ui = new(user, src, ui_key, "chem_dispenser.tmpl", ui_title, 390, 655)
// when the ui is first opened this is the data it will use
ui.set_initial_data(data)
// open the new ui window
@@ -164,7 +132,7 @@
amount = 100
if(href_list["dispense"])
- if (dispensable_reagents.Find(href_list["dispense"]) && beaker != null)
+ if (dispensable_reagents.Find(href_list["dispense"]) && beaker != null && beaker.is_open_container())
var/obj/item/weapon/reagent_containers/B = src.beaker
var/datum/reagents/R = B.reagents
var/space = R.maximum_volume - R.total_volume
@@ -184,19 +152,6 @@
/obj/machinery/chem_dispenser/attackby(var/obj/item/weapon/B as obj, var/mob/user as mob)
if(isrobot(user))
return
-
- if(broken_requirements.len && B.type == broken_requirements[1])
- broken_requirements -= broken_requirements[1]
- user << "You fix [src]."
- if(istype(B,/obj/item/stack))
- var/obj/item/stack/S = B
- S.use(1)
- else
- user.drop_item()
- del(B)
- if(broken_requirements.len==0)
- stat ^= BROKEN
- return
if(src.beaker)
user << "Something is already loaded into the machine."
return
@@ -210,21 +165,6 @@
nanomanager.update_uis(src) // update all UIs attached to src
return
- if(istype(B, /obj/item/weapon/wrench))
- if(moveable == 1)
- switch(anchored)
- if(0)
- playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1)
- spawn(10)
- user.visible_message("[user.name] secures [src.name] to the floor.", "You secure [src.name] to the floor.", "You hear a ratchet")
- anchored = 1
- if(1)
- playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1)
- spawn(10)
- user.visible_message("[user.name] unsecures [src.name] reinforcing bolts from the floor.", "You unsecure [src.name] from the floor.", "You hear a ratchet")
- anchored = 0
- return
-
/obj/machinery/chem_dispenser/attack_ai(mob/user as mob)
return src.attack_hand(user)
@@ -261,6 +201,7 @@
dispensable_reagents -= list("thirteenloko")
hackedcheck = 0
return
+
/obj/machinery/chem_dispenser/beer
icon_state = "booze_dispenser"
name = "booze dispenser"
@@ -297,7 +238,6 @@
icon_state = "mixer0"
use_power = 1
idle_power_usage = 20
- moveable = 1
var/beaker = null
var/obj/item/weapon/storage/pill_bottle/loaded_pill_bottle = null
var/mode = 0
@@ -333,30 +273,8 @@
del(src)
return
-/obj/machinery/chem_master/power_change()
- if(powered())
- stat &= ~NOPOWER
- else
- spawn(rand(0, 15))
- stat |= NOPOWER
-
/obj/machinery/chem_master/attackby(var/obj/item/weapon/B as obj, var/mob/user as mob)
- if(istype(B, /obj/item/weapon/wrench))
- if(moveable == 1)
- switch(anchored)
- if(0)
- playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1)
- spawn(10)
- user.visible_message("[user.name] secures [src.name] to the floor.", "You secure [src.name] to the floor.", "You hear a ratchet")
- anchored = 1
- if(1)
- playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1)
- spawn(10)
- user.visible_message("[user.name] unsecures [src.name] reinforcing bolts from the floor.", "You unsecure [src.name] from the floor.", "You hear a ratchet")
- anchored = 0
- return
-
if(istype(B, /obj/item/weapon/reagent_containers/glass))
if(src.beaker)
@@ -369,7 +287,7 @@
src.updateUsrDialog()
icon_state = "mixer1"
- if(istype(B, /obj/item/weapon/storage/pill_bottle))
+ else if(istype(B, /obj/item/weapon/storage/pill_bottle))
if(src.loaded_pill_bottle)
user << "A pill bottle is already loaded into the machine."
@@ -646,18 +564,16 @@
/obj/machinery/computer/pandemic/power_change()
-
+ ..()
if(stat & BROKEN)
icon_state = (src.beaker?"mixer1_b":"mixer0_b")
- else if(powered())
+ else if(!(stat & NOPOWER))
icon_state = (src.beaker?"mixer1":"mixer0")
- stat &= ~NOPOWER
else
spawn(rand(0, 15))
src.icon_state = (src.beaker?"mixer1_nopower":"mixer0_nopower")
- stat |= NOPOWER
/obj/machinery/computer/pandemic/Topic(href, href_list)
@@ -887,12 +803,11 @@
icon = 'icons/obj/kitchen.dmi'
icon_state = "juicer1"
layer = 2.9
- density = 1
- anchored = 1
+ density = 0
+ anchored = 0
use_power = 1
idle_power_usage = 5
active_power_usage = 100
- moveable = 1
var/inuse = 0
var/obj/item/weapon/reagent_containers/beaker = null
var/limit = 10
diff --git a/code/modules/reagents/Chemistry-Reagents.dm b/code/modules/reagents/Chemistry-Reagents.dm
index b16656d2..546b257a 100644
--- a/code/modules/reagents/Chemistry-Reagents.dm
+++ b/code/modules/reagents/Chemistry-Reagents.dm
@@ -5,10 +5,6 @@
#define REAGENTS_OVERDOSE 30
#define REM REAGENTS_EFFECT_MULTIPLIER
-//Some on_mob_life() procs check for alien races.
-#define IS_DIONA 1
-#define IS_VOX 2
-
//The reaction procs must ALWAYS set src = null, this detaches the proc from the object (the reagent)
//so that it can continue working when the reagent is deleted while the proc is still active.
@@ -26,6 +22,7 @@ datum
var/custom_metabolism = REAGENTS_METABOLISM
var/overdose = 0
var/overdose_dam = 1
+ var/scannable = 0 //shows up on health analyzers
//var/list/viruses = list()
var/color = "#000000" // rgb: 0, 0, 0 (does not support alpha channels - yet!)
@@ -241,7 +238,7 @@ datum
var/hotspot = (locate(/obj/fire) in T)
if(hotspot && !istype(T, /turf/space))
- var/datum/gas_mixture/lowertemp = T.remove_air( T:air:total_moles() )
+ var/datum/gas_mixture/lowertemp = T.remove_air( T:air:total_moles )
lowertemp.temperature = max( min(lowertemp.temperature-2000,lowertemp.temperature / 2) ,0)
lowertemp.react()
T.assume_air(lowertemp)
@@ -252,7 +249,7 @@ datum
var/turf/T = get_turf(O)
var/hotspot = (locate(/obj/fire) in T)
if(hotspot && !istype(T, /turf/space))
- var/datum/gas_mixture/lowertemp = T.remove_air( T:air:total_moles() )
+ var/datum/gas_mixture/lowertemp = T.remove_air( T:air:total_moles )
lowertemp.temperature = max( min(lowertemp.temperature-2000,lowertemp.temperature / 2) ,0)
lowertemp.react()
T.assume_air(lowertemp)
@@ -278,7 +275,7 @@ datum
description = "An ashen-obsidian-water mix, this solution will alter certain sections of the brain's rationality."
color = "#E0E8EF" // rgb: 224, 232, 239
- on_mob_life(var/mob/living/carbon/human/M as mob)
+ on_mob_life(var/mob/living/M as mob)
if(ishuman(M))
if((M.mind in ticker.mode.cult) && prob(10))
M << "\blue A cooling sensation from inside you brings you an untold calmness."
@@ -297,7 +294,6 @@ datum
holder.remove_reagent(src.id, 10 * REAGENTS_METABOLISM) //high metabolism to prevent extended uncult rolls.
return
-
lube
name = "Space Lube"
id = "lube"
@@ -431,6 +427,7 @@ datum
reagent_state = LIQUID
color = "#C8A5DC" // rgb: 200, 165, 220
overdose = REAGENTS_OVERDOSE*2
+ scannable = 1
on_mob_life(var/mob/living/M as mob, var/alien)
if(!M) M = holder.my_atom
@@ -808,6 +805,8 @@ datum
reagent_state = LIQUID
color = "#C855DC"
overdose = 60
+ scannable = 1
+ custom_metabolism = 0.025 // Lasts 10 minutes for 15 units
on_mob_life(var/mob/living/M as mob)
if (volume > overdose)
@@ -822,6 +821,8 @@ datum
reagent_state = LIQUID
color = "#C8A5DC"
overdose = 30
+ scannable = 1
+ custom_metabolism = 0.025 // Lasts 10 minutes for 15 units
on_mob_life(var/mob/living/M as mob)
if (volume > overdose)
@@ -836,6 +837,7 @@ datum
reagent_state = LIQUID
color = "#C805DC"
overdose = 20
+ custom_metabolism = 0.25 // Lasts 10 minutes for 15 units
on_mob_life(var/mob/living/M as mob)
if (volume > overdose)
@@ -865,6 +867,18 @@ datum
description = "Sterilizes wounds in preparation for surgery."
reagent_state = LIQUID
color = "#C8A5DC" // rgb: 200, 165, 220
+
+ //makes you squeaky clean
+ reaction_mob(var/mob/living/M, var/method=TOUCH, var/volume)
+ if (method == TOUCH)
+ M.germ_level -= min(volume*20, M.germ_level)
+
+ reaction_obj(var/obj/O, var/volume)
+ O.germ_level -= min(volume*20, O.germ_level)
+
+ reaction_turf(var/turf/T, var/volume)
+ T.germ_level -= min(volume*20, T.germ_level)
+
/* reaction_mob(var/mob/living/M, var/method=TOUCH, var/volume)
src = null
if (method==TOUCH)
@@ -1689,11 +1703,7 @@ datum
on_mob_life(var/mob/living/M as mob, var/alien)
if(!M) M = holder.my_atom
- if(alien && alien == IS_VOX)
- custom_metabolism = 10 //Inhaling O2 constantly puts plasma into voxblood which poisons them to hell. +Metab so one huff isn't death.
- toxpwr = 12 //This is a hacky way of doing it. They take 6 points of toxloss every four ticks (Breath tick). Still less than now.
- if(holder.has_reagent("inaprovaline"))
- holder.remove_reagent("inaprovaline", 2*REM)
+ holder.remove_reagent("inaprovaline", 2*REM)
..()
return
reaction_obj(var/obj/O, var/volume)
@@ -1704,24 +1714,10 @@ datum
egg.Hatch()*/
if((!O) || (!volume)) return 0
var/turf/the_turf = get_turf(O)
- var/datum/gas_mixture/napalm = new
- var/datum/gas/volatile_fuel/fuel = new
- fuel.moles = volume
- napalm.trace_gases += fuel
- the_turf.assume_air(napalm)
+ the_turf.assume_gas("volatile_fuel", volume, T20C)
reaction_turf(var/turf/T, var/volume)
src = null
- var/datum/gas_mixture/napalm = new
- var/datum/gas/volatile_fuel/fuel = new
- fuel.moles = volume
- napalm.trace_gases += fuel
- T.assume_air(napalm)
- return
- reaction_mob(var/mob/living/M, var/method=TOUCH, var/volume)//Splashing people with plasma is stronger than fuel!
- if(!istype(M, /mob/living))
- return
- if(method == TOUCH)
- M.adjust_fire_stacks(volume / 5)
+ T.assume_gas("volatile_fuel", volume, T20C)
return
toxin/lexorin
@@ -2550,7 +2546,7 @@ datum
T.wet_overlay = null
var/hotspot = (locate(/obj/fire) in T)
if(hotspot)
- var/datum/gas_mixture/lowertemp = T.remove_air( T:air:total_moles() )
+ var/datum/gas_mixture/lowertemp = T.remove_air( T:air:total_moles )
lowertemp.temperature = max( min(lowertemp.temperature-2000,lowertemp.temperature / 2) ,0)
lowertemp.react()
T.assume_air(lowertemp)
diff --git a/code/modules/reagents/Chemistry-Recipes.dm b/code/modules/reagents/Chemistry-Recipes.dm
index fba9c3c5..a6cc4928 100644
--- a/code/modules/reagents/Chemistry-Recipes.dm
+++ b/code/modules/reagents/Chemistry-Recipes.dm
@@ -413,16 +413,7 @@ datum
on_reaction(var/datum/reagents/holder, var/created_volume)
var/turf/location = get_turf(holder.my_atom.loc)
for(var/turf/simulated/floor/target_tile in range(0,location))
-
- var/datum/gas_mixture/napalm = new
- var/datum/gas/volatile_fuel/fuel = new
- fuel.moles = created_volume
- napalm.trace_gases += fuel
-
- napalm.temperature = 400+T0C
- napalm.update_values()
-
- target_tile.assume_air(napalm)
+ target_tile.assume_gas("volatile_fuel", created_volume, 400+T0C)
spawn (0) target_tile.hotspot_expose(700, 400)
holder.del_reagent("napalm")
return
@@ -854,8 +845,8 @@ datum
if(chosen)
// Calculate previous position for transition
- var/turf/FROM = get_turf_loc(holder.my_atom) // the turf of origin we're travelling FROM
- var/turf/TO = get_turf_loc(chosen) // the turf of origin we're travelling TO
+ var/turf/FROM = get_turf(holder.my_atom) // the turf of origin we're travelling FROM
+ var/turf/TO = get_turf(chosen) // the turf of origin we're travelling TO
playsound(TO, 'sound/effects/phasein.ogg', 100, 1)
@@ -916,16 +907,16 @@ datum
)//exclusion list for things you don't want the reaction to create.
var/list/critters = typesof(/mob/living/simple_animal/hostile) - blocked // list of possible hostile mobs
- playsound(get_turf_loc(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
+ playsound(get_turf(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
- for(var/mob/living/carbon/human/M in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/living/carbon/human/M in viewers(get_turf(holder.my_atom), null))
if(M:eyecheck() <= 0)
flick("e_flash", M.flash)
for(var/i = 1, i <= created_volume, i++)
var/chosen = pick(critters)
var/mob/living/simple_animal/hostile/C = new chosen
- C.loc = get_turf_loc(holder.my_atom)
+ C.loc = get_turf(holder.my_atom)
if(prob(50))
for(var/j = 1, j <= rand(1, 3), j++)
step(C, pick(NORTH,SOUTH,EAST,WEST))
@@ -942,9 +933,9 @@ datum
var/list/borks = typesof(/obj/item/weapon/reagent_containers/food/snacks) - /obj/item/weapon/reagent_containers/food/snacks
// BORK BORK BORK
- playsound(get_turf_loc(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
+ playsound(get_turf(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
- for(var/mob/living/carbon/human/M in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/living/carbon/human/M in viewers(get_turf(holder.my_atom), null))
if(M:eyecheck() <= 0)
flick("e_flash", M.flash)
@@ -952,7 +943,7 @@ datum
var/chosen = pick(borks)
var/obj/B = new chosen
if(B)
- B.loc = get_turf_loc(holder.my_atom)
+ B.loc = get_turf(holder.my_atom)
if(prob(50))
for(var/j = 1, j <= rand(1, 3), j++)
step(B, pick(NORTH,SOUTH,EAST,WEST))
@@ -1022,10 +1013,10 @@ datum
required_container = /obj/item/slime_extract/grey
required_other = 1
on_reaction(var/datum/reagents/holder)
- for(var/mob/O in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/O in viewers(get_turf(holder.my_atom), null))
O.show_message(text("\red Infused with plasma, the core begins to quiver and grow, and soon a new baby slime emerges from it!"), 1)
var/mob/living/carbon/slime/S = new /mob/living/carbon/slime
- S.loc = get_turf_loc(holder.my_atom)
+ S.loc = get_turf(holder.my_atom)
slimemonkey
@@ -1039,7 +1030,7 @@ datum
on_reaction(var/datum/reagents/holder)
for(var/i = 1, i <= 3, i++)
var /obj/item/weapon/reagent_containers/food/snacks/monkeycube/M = new /obj/item/weapon/reagent_containers/food/snacks/monkeycube
- M.loc = get_turf_loc(holder.my_atom)
+ M.loc = get_turf(holder.my_atom)
//Green
slimemutate
@@ -1063,10 +1054,10 @@ datum
on_reaction(var/datum/reagents/holder)
var/obj/item/stack/sheet/metal/M = new /obj/item/stack/sheet/metal
M.amount = 15
- M.loc = get_turf_loc(holder.my_atom)
+ M.loc = get_turf(holder.my_atom)
var/obj/item/stack/sheet/plasteel/P = new /obj/item/stack/sheet/plasteel
P.amount = 5
- P.loc = get_turf_loc(holder.my_atom)
+ P.loc = get_turf(holder.my_atom)
//Gold
slimecrit
@@ -1097,20 +1088,20 @@ datum
)//exclusion list for things you don't want the reaction to create.
var/list/critters = typesof(/mob/living/simple_animal/hostile) - blocked // list of possible hostile mobs
- playsound(get_turf_loc(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
+ playsound(get_turf(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
- for(var/mob/living/carbon/human/M in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/living/carbon/human/M in viewers(get_turf(holder.my_atom), null))
if(M:eyecheck() <= 0)
flick("e_flash", M.flash)
for(var/i = 1, i <= 5, i++)
var/chosen = pick(critters)
var/mob/living/simple_animal/hostile/C = new chosen
C.faction = "slimesummon"
- C.loc = get_turf_loc(holder.my_atom)
+ C.loc = get_turf(holder.my_atom)
if(prob(50))
for(var/j = 1, j <= rand(1, 3), j++)
step(C, pick(NORTH,SOUTH,EAST,WEST))
-// for(var/mob/O in viewers(get_turf_loc(holder.my_atom), null))
+// for(var/mob/O in viewers(get_turf(holder.my_atom), null))
// O.show_message(text("\red The slime core fizzles disappointingly,"), 1)
slimecatalyst
@@ -1123,7 +1114,7 @@ datum
required_other = 1
on_reaction(var/datum/reagents/holder)
var/obj/item/weapon/slimecatalyst/P = new /obj/item/weapon/slimecatalyst
- P.loc = get_turf_loc(holder.my_atom)
+ P.loc = get_turf(holder.my_atom)
/*
slimecritlesser
name = "Slime Crit Lesser"
@@ -1190,9 +1181,9 @@ datum
var/list/borks = typesof(/obj/item/weapon/reagent_containers/food/snacks) - /obj/item/weapon/reagent_containers/food/snacks
// BORK BORK BORK
- playsound(get_turf_loc(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
+ playsound(get_turf(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
- for(var/mob/living/carbon/human/M in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/living/carbon/human/M in viewers(get_turf(holder.my_atom), null))
if(M:eyecheck() <= 0)
flick("e_flash", M.flash)
@@ -1200,7 +1191,7 @@ datum
var/chosen = pick(borks)
var/obj/B = new chosen
if(B)
- B.loc = get_turf_loc(holder.my_atom)
+ B.loc = get_turf(holder.my_atom)
if(prob(50))
for(var/j = 1, j <= rand(1, 3), j++)
step(B, pick(NORTH,SOUTH,EAST,WEST))
@@ -1225,11 +1216,11 @@ datum
required_container = /obj/item/slime_extract/darkblue
required_other = 1
on_reaction(var/datum/reagents/holder)
- for(var/mob/O in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/O in viewers(get_turf(holder.my_atom), null))
O.show_message(text("\red The slime extract begins to vibrate violently !"), 1)
sleep(50)
- playsound(get_turf_loc(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
- for(var/mob/living/M in range (get_turf_loc(holder.my_atom), 7))
+ playsound(get_turf(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
+ for(var/mob/living/M in range (get_turf(holder.my_atom), 7))
M.bodytemperature -= 140
M << "\blue You feel a chill!"
@@ -1252,18 +1243,12 @@ datum
required_container = /obj/item/slime_extract/orange
required_other = 1
on_reaction(var/datum/reagents/holder)
- for(var/mob/O in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/O in viewers(get_turf(holder.my_atom), null))
O.show_message(text("\red The slime extract begins to vibrate violently !"), 1)
sleep(50)
var/turf/location = get_turf(holder.my_atom.loc)
for(var/turf/simulated/floor/target_tile in range(0,location))
-
- var/datum/gas_mixture/napalm = new
-
- napalm.toxins = 25
- napalm.temperature = 1400
-
- target_tile.assume_air(napalm)
+ target_tile.assume_gas("toxins", 25, 1400)
spawn (0) target_tile.hotspot_expose(700, 400)
//Yellow
@@ -1276,7 +1261,7 @@ datum
required_container = /obj/item/slime_extract/yellow
required_other = 1
on_reaction(var/datum/reagents/holder, var/created_volume)
- empulse(get_turf_loc(holder.my_atom), 3, 7)
+ empulse(get_turf(holder.my_atom), 3, 7)
slimecell
@@ -1289,7 +1274,7 @@ datum
required_other = 1
on_reaction(var/datum/reagents/holder, var/created_volume)
var/obj/item/weapon/cell/slime/P = new /obj/item/weapon/cell/slime
- P.loc = get_turf_loc(holder.my_atom)
+ P.loc = get_turf(holder.my_atom)
slimeglow
name = "Slime Glow"
@@ -1300,7 +1285,7 @@ datum
required_container = /obj/item/slime_extract/yellow
required_other = 1
on_reaction(var/datum/reagents/holder)
- for(var/mob/O in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/O in viewers(get_turf(holder.my_atom), null))
O.show_message(text("\red The contents of the slime core harden and begin to emit a warm, bright light."), 1)
var/obj/item/device/flashlight/slime/F = new /obj/item/device/flashlight/slime
F.loc = get_turf(holder.my_atom)
@@ -1317,7 +1302,7 @@ datum
required_other = 1
on_reaction(var/datum/reagents/holder)
var/obj/item/weapon/slimesteroid/P = new /obj/item/weapon/slimesteroid
- P.loc = get_turf_loc(holder.my_atom)
+ P.loc = get_turf(holder.my_atom)
@@ -1343,7 +1328,7 @@ datum
on_reaction(var/datum/reagents/holder)
var/obj/item/stack/sheet/mineral/plasma/P = new /obj/item/stack/sheet/mineral/plasma
P.amount = 10
- P.loc = get_turf_loc(holder.my_atom)
+ P.loc = get_turf(holder.my_atom)
//Red
slimeglycerol
@@ -1365,10 +1350,9 @@ datum
required_container = /obj/item/slime_extract/red
required_other = 1
on_reaction(var/datum/reagents/holder)
- for(var/mob/living/carbon/slime/slime in viewers(get_turf_loc(holder.my_atom), null))
- slime.tame = 0
+ for(var/mob/living/carbon/slime/slime in viewers(get_turf(holder.my_atom), null))
slime.rabid = 1
- for(var/mob/O in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/O in viewers(get_turf(holder.my_atom), null))
O.show_message(text("\red The [slime] is driven into a frenzy!."), 1)
//Pink
@@ -1382,7 +1366,7 @@ datum
required_other = 1
on_reaction(var/datum/reagents/holder)
var/obj/item/weapon/slimepotion/P = new /obj/item/weapon/slimepotion
- P.loc = get_turf_loc(holder.my_atom)
+ P.loc = get_turf(holder.my_atom)
//Black
@@ -1405,10 +1389,10 @@ datum
required_container = /obj/item/slime_extract/oil
required_other = 1
on_reaction(var/datum/reagents/holder)
- for(var/mob/O in viewers(get_turf_loc(holder.my_atom), null))
+ for(var/mob/O in viewers(get_turf(holder.my_atom), null))
O.show_message(text("\red The slime extract begins to vibrate violently !"), 1)
sleep(50)
- explosion(get_turf_loc(holder.my_atom), 1 ,3, 6)
+ explosion(get_turf(holder.my_atom), 1 ,3, 6)
//Light Pink
slimepotion2
name = "Slime Potion 2"
@@ -1420,7 +1404,7 @@ datum
required_other = 1
on_reaction(var/datum/reagents/holder)
var/obj/item/weapon/slimepotion2/P = new /obj/item/weapon/slimepotion2
- P.loc = get_turf_loc(holder.my_atom)
+ P.loc = get_turf(holder.my_atom)
//Adamantine
slimegolem
name = "Slime Golem"
@@ -1432,7 +1416,7 @@ datum
required_other = 1
on_reaction(var/datum/reagents/holder)
var/obj/effect/golemrune/Z = new /obj/effect/golemrune
- Z.loc = get_turf_loc(holder.my_atom)
+ Z.loc = get_turf(holder.my_atom)
Z.announce_to_ghosts()
//////////////////////////////////////////FOOD MIXTURES////////////////////////////////////
diff --git a/code/modules/reagents/grenade_launcher.dm b/code/modules/reagents/grenade_launcher.dm
index 6731c247..0c0e69fc 100644
--- a/code/modules/reagents/grenade_launcher.dm
+++ b/code/modules/reagents/grenade_launcher.dm
@@ -11,7 +11,7 @@
force = 5.0
var/list/grenades = new/list()
var/max_grenades = 3
- m_amt = 2000
+ matter = list("metal" = 2000)
examine()
set src in view()
@@ -56,7 +56,7 @@
var/obj/item/weapon/grenade/chem_grenade/F = grenades[1] //Now with less copypasta!
grenades -= F
F.loc = user.loc
- F.throw_at(target, 30, 2)
+ F.throw_at(target, 30, 2, user)
message_admins("[key_name_admin(user)] fired a grenade ([F.name]) from a grenade launcher ([src.name]) (JMP)")
message_mods("[key_name_admin(user)] fired a grenade ([F.name]) from a grenade launcher ([src.name]) (JMP)")
log_game("[key_name_admin(user)] used a grenade ([src.name]).")
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index 8f4eb31f..50ec2f70 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -28,7 +28,9 @@
return
/obj/item/weapon/reagent_containers/attack(mob/M as mob, mob/user as mob, def_zone)
- return
+ if (can_operate(M)) //Checks if mob is lying down on table for surgery
+ if (do_surgery(M,user,src))
+ return
// this prevented pills, food, and other things from being picked up by bags.
// possibly intentional, but removing it allows us to not duplicate functionality.
diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm
index 9f3a9ea5..c091f1db 100644
--- a/code/modules/reagents/reagent_containers/blood_pack.dm
+++ b/code/modules/reagents/reagent_containers/blood_pack.dm
@@ -18,25 +18,11 @@
update_icon()
update_icon()
-
-
-
-
-/*
- if(blood_type != "K")
- var/percent = round((reagents.total_volume / volume) * 100)
- switch(percent)
- if(0 to 9) icon_state = "empty"
- if(10 to 50) icon_state = "half"
- if(51 to INFINITY) icon_state = "full"
- else
- var/percent2 = round((reagents.total_volume / volume) * 100)
- switch(percent2)
- if(0 to 9) icon_state = "empty"
- if(10 to 50) icon_state = "kocas_half"
- if(51 to INFINITY) icon_state = "kocas_full"
-
-*/
+ var/percent = round((reagents.total_volume / volume) * 100)
+ switch(percent)
+ if(0 to 9) icon_state = "empty"
+ if(10 to 50) icon_state = "half"
+ if(51 to INFINITY) icon_state = "full"
/obj/item/weapon/reagent_containers/blood/APlus
blood_type = "A+"
diff --git a/code/modules/reagents/reagent_containers/borghydro.dm b/code/modules/reagents/reagent_containers/borghydro.dm
index e4af24a5..5e7e7572 100644
--- a/code/modules/reagents/reagent_containers/borghydro.dm
+++ b/code/modules/reagents/reagent_containers/borghydro.dm
@@ -18,6 +18,12 @@
var/list/reagent_ids = list("tricordrazine", "inaprovaline", "spaceacillin")
//var/list/reagent_ids = list("dexalin", "kelotane", "bicaridine", "anti_toxin", "inaprovaline", "spaceacillin")
+/obj/item/weapon/reagent_containers/borghypo/surgeon
+ reagent_ids = list("bicaridine", "inaprovaline", "dexalin")
+
+/obj/item/weapon/reagent_containers/borghypo/crisis
+ reagent_ids = list("tricordrazine", "inaprovaline", "tramadol")
+
/obj/item/weapon/reagent_containers/borghypo/New()
..()
for(var/R in reagent_ids)
diff --git a/code/modules/reagents/reagent_containers/food.dm b/code/modules/reagents/reagent_containers/food.dm
index f87f2735..793f5fb1 100644
--- a/code/modules/reagents/reagent_containers/food.dm
+++ b/code/modules/reagents/reagent_containers/food.dm
@@ -6,7 +6,29 @@
volume = 50 //Sets the default container amount for all food items.
var/filling_color = "#FFFFFF" //Used by sandwiches.
+ var/list/center_of_mass = newlist() //Center of mass
+
/obj/item/weapon/reagent_containers/food/New()
- ..()
- src.pixel_x = rand(-10.0, 10) //Randomizes postion
- src.pixel_y = rand(-10.0, 10)
\ No newline at end of file
+ ..()
+ if (!pixel_x && !pixel_y)
+ src.pixel_x = rand(-6.0, 6) //Randomizes postion
+ src.pixel_y = rand(-6.0, 6)
+
+/obj/item/weapon/reagent_containers/food/afterattack(atom/A, mob/user, proximity, params)
+ if(proximity && params && istype(A, /obj/structure/table) && center_of_mass.len)
+ //Places the item on a grid
+ var/list/mouse_control = params2list(params)
+ var/cellnumber = 4
+
+ var/mouse_x = text2num(mouse_control["icon-x"])
+ var/mouse_y = text2num(mouse_control["icon-y"])
+
+ var/grid_x = round(mouse_x, 32/cellnumber)
+ var/grid_y = round(mouse_y, 32/cellnumber)
+
+ if(mouse_control["icon-x"])
+ var/sign = mouse_x - grid_x != 0 ? sign(mouse_x - grid_x) : -1 //positive if rounded down, else negative
+ pixel_x = grid_x - center_of_mass["x"] + sign*16/cellnumber //center of the cell
+ if(mouse_control["icon-y"])
+ var/sign = mouse_y - grid_y != 0 ? sign(mouse_y - grid_y) : -1
+ pixel_y = grid_y - center_of_mass["y"] + sign*16/cellnumber
\ No newline at end of file
diff --git a/code/modules/reagents/reagent_containers/food/drinks/drinkingglass.dm b/code/modules/reagents/reagent_containers/food/drinks/drinkingglass.dm
index 0b10f15f..24e05cbc 100644
--- a/code/modules/reagents/reagent_containers/food/drinks/drinkingglass.dm
+++ b/code/modules/reagents/reagent_containers/food/drinks/drinkingglass.dm
@@ -5,7 +5,7 @@
desc = "Your standard drinking glass."
icon_state = "glass_empty"
amount_per_transfer_from_this = 10
- g_amt = 500
+ matter = list("glass" = 500)
volume = 50
on_reagent_change()
diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm
index 7ff400b8..5d7a95c4 100644
--- a/code/modules/reagents/reagent_containers/glass.dm
+++ b/code/modules/reagents/reagent_containers/glass.dm
@@ -95,6 +95,7 @@
spawn(5) src.reagents.clear_reagents()
return
else if(istype(target, /obj/structure/reagent_dispensers)) //A dispenser. Transfer FROM it TO us.
+ target.add_fingerprint(user)
if(!target.reagents.total_volume && target.reagents)
user << "\red [target] is empty."
@@ -108,6 +109,7 @@
user << "\blue You fill [src] with [trans] units of the contents of [target]."
else if(target.is_open_container() && target.reagents) //Something like a glass. Player probably wants to transfer TO it.
+
if(!reagents.total_volume)
user << "\red [src] is empty."
return
@@ -160,8 +162,7 @@
icon = 'icons/obj/chemical.dmi'
icon_state = "beaker"
item_state = "beaker"
- m_amt = 0
- g_amt = 500
+ matter = list("glass" = 500)
on_reagent_change()
update_icon()
@@ -205,7 +206,7 @@
name = "large beaker"
desc = "A large beaker. Can hold up to 100 units."
icon_state = "beakerlarge"
- g_amt = 5000
+ matter = list("glass" = 5000)
volume = 100
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,25,30,50,100)
@@ -215,7 +216,7 @@
name = "cryostasis beaker"
desc = "A cryostasis beaker that allows for chemical storage without reactions. Can hold up to 50 units."
icon_state = "beakernoreact"
- g_amt = 500
+ matter = list("glass" = 500)
volume = 50
amount_per_transfer_from_this = 10
flags = FPRINT | TABLEPASS | OPENCONTAINER | NOREACT
@@ -224,7 +225,7 @@
name = "bluespace beaker"
desc = "A bluespace beaker, powered by experimental bluespace technology. Can hold up to 300 units."
icon_state = "beakerbluespace"
- g_amt = 5000
+ matter = list("glass" = 5000)
volume = 300
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,25,30,50,100,300)
@@ -235,7 +236,7 @@
name = "vial"
desc = "A small glass vial. Can hold up to 25 units."
icon_state = "vial"
- g_amt = 250
+ matter = list("glass" = 250)
volume = 25
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,25)
@@ -265,8 +266,7 @@
icon = 'icons/obj/janitor.dmi'
icon_state = "bucket"
item_state = "bucket"
- m_amt = 200
- g_amt = 0
+ matter = list("metal" = 200)
w_class = 3.0
amount_per_transfer_from_this = 20
possible_transfer_amounts = list(10,20,30,50,70)
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index 2fb1e3eb..b3b06c49 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -11,7 +11,7 @@
icon = 'icons/obj/syringe.dmi'
item_state = "syringe_0"
icon_state = "0"
- g_amt = 150
+ matter = list("glass" = 150)
amount_per_transfer_from_this = 5
possible_transfer_amounts = null //list(5,10,15)
volume = 15
@@ -236,7 +236,7 @@
if(istype(target, /mob/living/carbon/human))
- var/target_zone = check_zone(user.zone_sel.selecting, target)
+ var/target_zone = ran_zone(check_zone(user.zone_sel.selecting, target))
var/datum/organ/external/affecting = target:get_organ(target_zone)
if (!affecting)
@@ -436,6 +436,17 @@
mode = SYRINGE_INJECT
update_icon()
+/obj/item/weapon/reagent_containers/syringe/drugs
+ name = "Syringe (drugs)"
+ desc = "Contains aggressive drugs meant for torture."
+ New()
+ ..()
+ reagents.add_reagent("space_drugs", 5)
+ reagents.add_reagent("mindbreaker", 5)
+ reagents.add_reagent("cryptobiolin", 5)
+ mode = SYRINGE_INJECT
+ update_icon()
+
/obj/item/weapon/reagent_containers/ld50_syringe/choral
New()
..()
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 70908cfe..861c8f58 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -116,6 +116,7 @@
overlays = new/list()
/obj/structure/reagent_dispensers/fueltank/attackby(obj/item/weapon/W as obj, mob/user as mob)
+ src.add_fingerprint(user)
if (istype(W,/obj/item/weapon/wrench))
user.visible_message("[user] wrenches [src]'s faucet [modded ? "closed" : "open"].", \
"You wrench [src]'s faucet [modded ? "closed" : "open"]")
@@ -164,6 +165,10 @@
/obj/structure/reagent_dispensers/fueltank/bullet_act(var/obj/item/projectile/Proj)
if(istype(Proj ,/obj/item/projectile/beam)||istype(Proj,/obj/item/projectile/bullet))
+ if(istype(Proj.firer))
+ message_admins("[key_name_admin(Proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]) (JMP).")
+ log_game("[key_name(Proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]).")
+
if(!istype(Proj ,/obj/item/projectile/beam/lastertag) && !istype(Proj ,/obj/item/projectile/beam/practice) )
explode()
@@ -200,7 +205,7 @@
amount = min(amount, reagents.total_volume)
reagents.remove_reagent("fuel",amount)
- new /obj/effect/decal/cleanable/liquid_fuel(src.loc, amount)
+ new /obj/effect/decal/cleanable/liquid_fuel(src.loc, amount,1)
/obj/structure/reagent_dispensers/peppertank
name = "Pepper Spray Refiller"
diff --git a/code/modules/reagents/syringe_gun.dm b/code/modules/reagents/syringe_gun.dm
index 7c3d9b37..5a684f0b 100644
--- a/code/modules/reagents/syringe_gun.dm
+++ b/code/modules/reagents/syringe_gun.dm
@@ -13,7 +13,7 @@
force = 4.0
var/list/syringes = new/list()
var/max_syringes = 1
- m_amt = 2000
+ matter = list("metal" = 2000)
/obj/item/weapon/gun/syringe/examine()
set src in view()
diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm
index cbd47957..1f973b22 100644
--- a/code/modules/recycling/conveyor2.dm
+++ b/code/modules/recycling/conveyor2.dm
@@ -6,6 +6,7 @@
icon_state = "conveyor0"
name = "conveyor belt"
desc = "A conveyor belt."
+ layer = 2 // so they appear under stuff
anchored = 1
var/operating = 0 // 1 if running forward, -1 if backwards, 0 if off
var/operable = 1 // true if can operate (no broken segments in this belt run)
diff --git a/code/modules/recycling/disposal-construction.dm b/code/modules/recycling/disposal-construction.dm
index fd65a573..8b123b93 100644
--- a/code/modules/recycling/disposal-construction.dm
+++ b/code/modules/recycling/disposal-construction.dm
@@ -10,7 +10,7 @@
anchored = 0
density = 0
pressure_resistance = 5*ONE_ATMOSPHERE
- m_amt = 1850
+ matter = list("metal" = 1850)
level = 2
var/ptype = 0
// 0=straight, 1=bent, 2=junction-j1, 3=junction-j2, 4=junction-y, 5=trunk, 6=disposal bin, 7=outlet, 8=inlet
@@ -205,24 +205,6 @@
return
var/obj/structure/disposalpipe/CP = locate() in T
- if(ptype>=6 && ptype <= 8) // Disposal or outlet
- if(CP) // There's something there
- if(!istype(CP,/obj/structure/disposalpipe/trunk))
- user << "The [nicetype] requires a trunk underneath it in order to work."
- return
- else // Nothing under, fuck.
- user << "The [nicetype] requires a trunk underneath it in order to work."
- return
- else
- if(CP)
- update()
- var/pdir = CP.dpdir
- if(istype(CP, /obj/structure/disposalpipe/broken))
- pdir = CP.dir
- if(pdir & dpdir)
- user << "There is already a [nicetype] at that location."
- return
-
if(istype(I, /obj/item/weapon/wrench))
if(anchored)
@@ -234,7 +216,25 @@
density = 1
user << "You detach the [nicetype] from the underfloor."
else
- anchored = 1
+ if(ptype>=6 && ptype <= 8) // Disposal or outlet
+ if(CP) // There's something there
+ if(!istype(CP,/obj/structure/disposalpipe/trunk))
+ user << "The [nicetype] requires a trunk underneath it in order to work."
+ return
+ else // Nothing under, fuck.
+ user << "The [nicetype] requires a trunk underneath it in order to work."
+ return
+ else
+ if(CP)
+ update()
+ var/pdir = CP.dpdir
+ if(istype(CP, /obj/structure/disposalpipe/broken))
+ pdir = CP.dir
+ if(pdir & dpdir)
+ user << "There is already a [nicetype] at that location."
+ return
+
+ anchored = 1
if(ispipe)
level = 1 // We don't want disposal bins to disappear under the floors
density = 0
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index 82a3f9b9..12478e0a 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -5,7 +5,9 @@
// Automatically recharges air (unless off), will flush when ready if pre-set
// Can hold items and human size things, no other draggables
// Toilets are a type of disposal bin for small objects only and work on magic. By magic, I mean torque rotation
-#define SEND_PRESSURE 0.05*ONE_ATMOSPHERE
+#define SEND_PRESSURE 50 //kPa
+#define PRESSURE_TANK_VOLUME 70 //L - a 0.3 m diameter * 1 m long cylindrical tank. Happens to be the same volume as the regular oxygen tanks, so seems appropriate.
+#define PUMP_MAX_FLOW_RATE 50 //L/s - 8 m/s using a 15 cm by 15 cm inlet
/obj/machinery/disposal
name = "disposal unit"
@@ -22,470 +24,459 @@
var/flush_every_ticks = 30 //Every 30 ticks it will look whether it is ready to flush
var/flush_count = 0 //this var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush.
var/last_sound = 0
- active_power_usage = 600
+ active_power_usage = 3500 //the pneumatic pump power. 3 HP ~ 2200W
idle_power_usage = 100
- // create a new disposal
- // find the attached trunk (if present) and init gas resvr.
- New()
- ..()
- spawn(5)
- trunk = locate() in src.loc
- if(!trunk)
- mode = 0
- flush = 0
- else
- trunk.linked = src // link the pipe trunk to self
-
- air_contents = new/datum/gas_mixture()
- //gas.volume = 1.05 * CELLSTANDARD
- update()
-
-
- // attack by item places it in to disposal
- attackby(var/obj/item/I, var/mob/user)
- if(stat & BROKEN || !I || !user)
- return
-
- if(isrobot(user) && !istype(I, /obj/item/weapon/storage/bag/trash))
- return
- src.add_fingerprint(user)
- if(mode<=0) // It's off
- if(istype(I, /obj/item/weapon/screwdriver))
- if(contents.len > 0)
- user << "Eject the items first!"
- return
- if(mode==0) // It's off but still not unscrewed
- mode=-1 // Set it to doubleoff l0l
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
- user << "You remove the screws around the power connection."
- return
- else if(mode==-1)
- mode=0
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
- user << "You attach the screws around the power connection."
- return
- else if(istype(I,/obj/item/weapon/weldingtool) && mode==-1)
- if(contents.len > 0)
- user << "Eject the items first!"
- return
- var/obj/item/weapon/weldingtool/W = I
- if(W.remove_fuel(0,user))
- playsound(src.loc, 'sound/items/Welder2.ogg', 100, 1)
- user << "You start slicing the floorweld off the disposal unit."
-
- if(do_after(user,20))
- if(!src || !W.isOn()) return
- user << "You sliced the floorweld off the disposal unit."
- var/obj/structure/disposalconstruct/C = new (src.loc)
- src.transfer_fingerprints_to(C)
- C.ptype = 6 // 6 = disposal unit
- C.anchored = 1
- C.density = 1
- C.update()
- del(src)
- return
- else
- user << "You need more welding fuel to complete this task."
- return
-
- if(istype(I, /obj/item/weapon/melee/energy/blade))
- user << "You can't place that item inside the disposal unit."
- return
-
- if(istype(I, /obj/item/weapon/storage/bag/trash))
- var/obj/item/weapon/storage/bag/trash/T = I
- user << "\blue You empty the bag."
- for(var/obj/item/O in T.contents)
- T.remove_from_storage(O,src)
- T.update_icon()
- update()
- return
-
- var/obj/item/weapon/grab/G = I
- if(istype(G)) // handle grabbed mob
- if(ismob(G.affecting))
- var/mob/GM = G.affecting
- for (var/mob/V in viewers(usr))
- V.show_message("[usr] starts putting [GM.name] into the disposal.", 3)
- if(do_after(usr, 20))
- if (GM.client)
- GM.client.perspective = EYE_PERSPECTIVE
- GM.client.eye = src
- GM.loc = src
- for (var/mob/C in viewers(src))
- C.show_message("\red [GM.name] has been placed in the [src] by [user].", 3)
- del(G)
- usr.attack_log += text("\[[time_stamp()]\] Has placed [GM.name] ([GM.ckey]) in disposals.")
- GM.attack_log += text("\[[time_stamp()]\] Has been placed in disposals by [usr.name] ([usr.ckey])")
- msg_admin_attack("[usr] ([usr.ckey]) placed [GM] ([GM.ckey]) in a disposals unit. (JMP)")
- return
-
- if(!I) return
-
- user.drop_item()
- if(I)
- I.loc = src
-
- user << "You place \the [I] into the [src]."
- for(var/mob/M in viewers(src))
- if(M == user)
- continue
- M.show_message("[user.name] places \the [I] into the [src].", 3)
-
- update()
-
- // mouse drop another mob or self
- //
- MouseDrop_T(mob/target, mob/user)
- if (!istype(target) || target.buckled || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || istype(user, /mob/living/silicon/ai))
- return
- if(isanimal(user) && target != user) return //animals cannot put mobs other than themselves into disposal
- src.add_fingerprint(user)
- var/target_loc = target.loc
- var/msg
- for (var/mob/V in viewers(usr))
- if(target == user && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
- V.show_message("[usr] starts climbing into the disposal.", 3)
- if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
- if(target.anchored) return
- V.show_message("[usr] starts stuffing [target.name] into the disposal.", 3)
- if(!do_after(usr, 20))
- return
- if(target_loc != target.loc)
- return
- if(target == user && !user.stat && !user.weakened && !user.stunned && !user.paralysis) // if drop self, then climbed in
- // must be awake, not stunned or whatever
- msg = "[user.name] climbs into the [src]."
- user << "You climb into the [src]."
- else if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
- msg = "[user.name] stuffs [target.name] into the [src]!"
- user << "You stuff [target.name] into the [src]!"
-
- user.attack_log += text("\[[time_stamp()]\] Has placed [target.name] ([target.ckey]) in disposals.")
- target.attack_log += text("\[[time_stamp()]\] Has been placed in disposals by [user.name] ([user.ckey])")
- msg_admin_attack("[user] ([user.ckey]) placed [target] ([target.ckey]) in a disposals unit. (JMP)")
- else
- return
- if (target.client)
- target.client.perspective = EYE_PERSPECTIVE
- target.client.eye = src
- target.loc = src
-
- for (var/mob/C in viewers(src))
- if(C == user)
- continue
- C.show_message(msg, 3)
-
- update()
- return
-
- // can breath normally in the disposal
- alter_health()
- return get_turf(src)
-
- // attempt to move while inside
- relaymove(mob/user as mob)
- if(user.stat || src.flushing)
- return
- src.go_out(user)
- return
-
- // leave the disposal
- proc/go_out(mob/user)
-
- if (user.client)
- user.client.eye = user.client.mob
- user.client.perspective = MOB_PERSPECTIVE
- user.loc = src.loc
- update()
- return
-
-
- // monkeys can only pull the flush lever
- attack_paw(mob/user as mob)
- if(stat & BROKEN)
- return
-
- flush = !flush
- update()
- return
-
- // ai as human but can't flush
- attack_ai(mob/user as mob)
- interact(user, 1)
-
- // human interact with machine
- attack_hand(mob/user as mob)
- if(user && user.loc == src)
- usr << "\red You cannot reach the controls from inside."
- return
- /*
- if(mode==-1)
- usr << "\red The disposal units power is disabled."
- return
- */
- interact(user, 0)
-
- // user interaction
- interact(mob/user, var/ai=0)
-
- src.add_fingerprint(user)
-
- if(istype(user, /mob/living/carbon/human))
- var/mob/living/carbon/human/M = user
- if(M.h_style == "Floorlength Braid" || M.h_style == "Very Long Hair")
- if(prob(5))
- M.apply_damage(30, BRUTE, "head")
- M.apply_damage(45, HALLOSS)
- M.visible_message("\red [user]'s hair catches in the [src]!", "\red Your hair gets caught in the [src]!")
- M.say("*scream")
-
- if(stat & BROKEN)
- user.unset_machine()
- return
-
- var/dat = "Waste Disposal UnitWaste Disposal Unit "
-
- if(!ai) // AI can't pull flush handle
- if(flush)
- dat += "Disposal handle: Disengage Engaged"
- else
- dat += "Disposal handle: Disengaged Engage"
-
- dat += "
Eject contents "
-
- if(mode <= 0)
- dat += "Pump: Off On "
- else if(mode == 1)
- dat += "Pump: Off On (pressurizing) "
- else
- dat += "Pump: Off On (idle) "
-
- var/per = 100* air_contents.return_pressure() / (SEND_PRESSURE)
-
- dat += "Pressure: [round(per, 1)]% "
-
-
- user.set_machine(src)
- user << browse(dat, "window=disposal;size=360x170")
- onclose(user, "disposal")
-
- // handle machine interaction
-
- Topic(href, href_list)
- if(usr.loc == src)
- usr << "\red You cannot reach the controls from inside."
- return
-
- if(mode==-1 && !href_list["eject"]) // only allow ejecting if mode is -1
- usr << "\red The disposal units power is disabled."
- return
- ..()
- src.add_fingerprint(usr)
- if(stat & BROKEN)
- return
- if(usr.stat || usr.restrained() || src.flushing)
- return
-
- if (in_range(src, usr) && istype(src.loc, /turf))
- usr.set_machine(src)
-
- if(href_list["close"])
- usr.unset_machine()
- usr << browse(null, "window=disposal")
- return
-
- if(href_list["pump"])
- if(text2num(href_list["pump"]))
- mode = 1
- else
- mode = 0
- update()
-
- if(href_list["handle"])
- flush = text2num(href_list["handle"])
- update()
-
- if(href_list["eject"])
- eject()
- else
- usr << browse(null, "window=disposal")
- usr.unset_machine()
- return
- return
-
- // eject the contents of the disposal unit
- proc/eject()
- for(var/atom/movable/AM in src)
- AM.loc = src.loc
- AM.pipe_eject(0)
- update()
-
- // update the icon & overlays to reflect mode & status
- proc/update()
- overlays.Cut()
- if(stat & BROKEN)
- icon_state = "disposal-broken"
+// create a new disposal
+// find the attached trunk (if present) and init gas resvr.
+/obj/machinery/disposal/New()
+ ..()
+ spawn(5)
+ trunk = locate() in src.loc
+ if(!trunk)
mode = 0
flush = 0
- return
+ else
+ trunk.linked = src // link the pipe trunk to self
- // flush handle
- if(flush)
- overlays += image('icons/obj/pipes/disposal.dmi', "dispover-handle")
-
- // only handle is shown if no power
- if(stat & NOPOWER || mode == -1)
- return
-
- // check for items in disposal - occupied light
- if(contents.len > 0)
- overlays += image('icons/obj/pipes/disposal.dmi', "dispover-full")
-
- // charging and ready light
- if(mode == 1)
- overlays += image('icons/obj/pipes/disposal.dmi', "dispover-charge")
- else if(mode == 2)
- overlays += image('icons/obj/pipes/disposal.dmi', "dispover-ready")
-
- // timed process
- // charge the gas reservoir and perform flush if ready
- process()
- use_power = 0
- if(stat & BROKEN) // nothing can happen if broken
- return
-
- if(!air_contents) // Potentially causes a runtime otherwise (if this is really shitty, blame pete //Donkie)
- return
-
- flush_count++
- if( flush_count >= flush_every_ticks )
- if( contents.len )
- if(mode == 2)
- spawn(0)
- feedback_inc("disposal_auto_flush",1)
- flush()
- flush_count = 0
-
- src.updateDialog()
-
- if(flush && air_contents.return_pressure() >= SEND_PRESSURE ) // flush can happen even without power
- flush()
-
- if(stat & NOPOWER) // won't charge if no power
- return
-
- use_power = 1
-
- if(mode != 1) // if off or ready, no need to charge
- return
-
- // otherwise charge
- use_power = 2
-
- var/atom/L = loc // recharging from loc turf
-
- var/datum/gas_mixture/env = L.return_air()
- var/pressure_delta = (SEND_PRESSURE*1.01) - air_contents.return_pressure()
-
- if(env.temperature > 0)
- var/transfer_moles = 0.1 * pressure_delta*air_contents.volume/(env.temperature * R_IDEAL_GAS_EQUATION)
-
- //Actually transfer the gas
- var/datum/gas_mixture/removed = env.remove(transfer_moles)
- air_contents.merge(removed)
+ air_contents = new/datum/gas_mixture()
+ air_contents.volume = PRESSURE_TANK_VOLUME
+ update()
- // if full enough, switch to ready mode
- if(air_contents.return_pressure() >= SEND_PRESSURE)
- mode = 2
- update()
+// attack by item places it in to disposal
+/obj/machinery/disposal/attackby(var/obj/item/I, var/mob/user)
+ if(stat & BROKEN || !I || !user)
return
- // perform a flush
- proc/flush()
+ if(isrobot(user) && !istype(I, /obj/item/weapon/storage/bag/trash))
+ return
+ src.add_fingerprint(user)
+ if(mode<=0) // It's off
+ if(istype(I, /obj/item/weapon/screwdriver))
+ if(contents.len > 0)
+ user << "Eject the items first!"
+ return
+ if(mode==0) // It's off but still not unscrewed
+ mode=-1 // Set it to doubleoff l0l
+ playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
+ user << "You remove the screws around the power connection."
+ return
+ else if(mode==-1)
+ mode=0
+ playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
+ user << "You attach the screws around the power connection."
+ return
+ else if(istype(I,/obj/item/weapon/weldingtool) && mode==-1)
+ if(contents.len > 0)
+ user << "Eject the items first!"
+ return
+ var/obj/item/weapon/weldingtool/W = I
+ if(W.remove_fuel(0,user))
+ playsound(src.loc, 'sound/items/Welder2.ogg', 100, 1)
+ user << "You start slicing the floorweld off the disposal unit."
- flushing = 1
- flick("[icon_state]-flush", src)
+ if(do_after(user,20))
+ if(!src || !W.isOn()) return
+ user << "You sliced the floorweld off the disposal unit."
+ var/obj/structure/disposalconstruct/C = new (src.loc)
+ src.transfer_fingerprints_to(C)
+ C.ptype = 6 // 6 = disposal unit
+ C.anchored = 1
+ C.density = 1
+ C.update()
+ del(src)
+ return
+ else
+ user << "You need more welding fuel to complete this task."
+ return
- var/wrapcheck = 0
- var/obj/structure/disposalholder/H = new() // virtual holder object which actually
- // travels through the pipes.
- //Hacky test to get drones to mail themselves through disposals.
- for(var/mob/living/silicon/robot/drone/D in src)
- wrapcheck = 1
+ if(istype(I, /obj/item/weapon/melee/energy/blade))
+ user << "You can't place that item inside the disposal unit."
+ return
- for(var/obj/item/smallDelivery/O in src)
- wrapcheck = 1
-
- if(wrapcheck == 1)
- H.tomail = 1
-
-
- air_contents = new() // new empty gas resv.
-
- sleep(10)
- if(last_sound < world.time + 1)
- playsound(src, 'sound/machines/disposalflush.ogg', 50, 0, 0)
- last_sound = world.time
- sleep(5) // wait for animation to finish
-
-
- H.init(src) // copy the contents of disposer to holder
-
- H.start(src) // start the holder processing movement
- flushing = 0
- // now reset disposal state
- flush = 0
- if(mode == 2) // if was ready,
- mode = 1 // switch to charging
+ if(istype(I, /obj/item/weapon/storage/bag/trash))
+ var/obj/item/weapon/storage/bag/trash/T = I
+ user << "\blue You empty the bag."
+ for(var/obj/item/O in T.contents)
+ T.remove_from_storage(O,src)
+ T.update_icon()
update()
return
-
- // called when area power changes
- power_change()
- ..() // do default setting/reset of stat NOPOWER bit
- update() // update icon
+ var/obj/item/weapon/grab/G = I
+ if(istype(G)) // handle grabbed mob
+ if(ismob(G.affecting))
+ var/mob/GM = G.affecting
+ for (var/mob/V in viewers(usr))
+ V.show_message("[usr] starts putting [GM.name] into the disposal.", 3)
+ if(do_after(usr, 20))
+ if (GM.client)
+ GM.client.perspective = EYE_PERSPECTIVE
+ GM.client.eye = src
+ GM.loc = src
+ for (var/mob/C in viewers(src))
+ C.show_message("\red [GM.name] has been placed in the [src] by [user].", 3)
+ del(G)
+ usr.attack_log += text("\[[time_stamp()]\] Has placed [GM.name] ([GM.ckey]) in disposals.")
+ GM.attack_log += text("\[[time_stamp()]\] Has been placed in disposals by [usr.name] ([usr.ckey])")
+ msg_admin_attack("[usr] ([usr.ckey]) placed [GM] ([GM.ckey]) in a disposals unit. (JMP)")
return
+ if(!I) return
- // called when holder is expelled from a disposal
- // should usually only occur if the pipe network is modified
- proc/expel(var/obj/structure/disposalholder/H)
+ user.drop_item()
+ if(I)
+ I.loc = src
- var/turf/target
- playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
- if(H) // Somehow, someone managed to flush a window which broke mid-transit and caused the disposal to go in an infinite loop trying to expel null, hopefully this fixes it
- for(var/atom/movable/AM in H)
- target = get_offset_target_turf(src.loc, rand(5)-rand(5), rand(5)-rand(5))
+ user << "You place \the [I] into the [src]."
+ for(var/mob/M in viewers(src))
+ if(M == user)
+ continue
+ M.show_message("[user.name] places \the [I] into the [src].", 3)
- AM.loc = src.loc
- AM.pipe_eject(0)
- if(!istype(AM,/mob/living/silicon/robot/drone)) //Poor drones kept smashing windows and taking system damage being fired out of disposals. ~Z
- spawn(1)
- if(AM)
- AM.throw_at(target, 5, 1)
+ update()
- H.vent_gas(loc)
- del(H)
+// mouse drop another mob or self
+//
+/obj/machinery/disposal/MouseDrop_T(mob/target, mob/user)
+ if (!istype(target) || target.buckled || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || istype(user, /mob/living/silicon/ai))
+ return
+ if(isanimal(user) && target != user) return //animals cannot put mobs other than themselves into disposal
+ src.add_fingerprint(user)
+ var/target_loc = target.loc
+ var/msg
+ for (var/mob/V in viewers(usr))
+ if(target == user && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
+ V.show_message("[usr] starts climbing into the disposal.", 3)
+ if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
+ if(target.anchored) return
+ V.show_message("[usr] starts stuffing [target.name] into the disposal.", 3)
+ if(!do_after(usr, 20))
+ return
+ if(target_loc != target.loc)
+ return
+ if(target == user && !user.stat && !user.weakened && !user.stunned && !user.paralysis) // if drop self, then climbed in
+ // must be awake, not stunned or whatever
+ msg = "[user.name] climbs into the [src]."
+ user << "You climb into the [src]."
+ else if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
+ msg = "[user.name] stuffs [target.name] into the [src]!"
+ user << "You stuff [target.name] into the [src]!"
- CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
- if (istype(mover,/obj/item) && mover.throwing)
- var/obj/item/I = mover
- if(istype(I, /obj/item/projectile))
- return
- if(prob(75))
- I.loc = src
- for(var/mob/M in viewers(src))
- M.show_message("\the [I] lands in \the [src].", 3)
- else
- for(var/mob/M in viewers(src))
- M.show_message("\the [I] bounces off of \the [src]'s rim!.", 3)
- return 0
+ user.attack_log += text("\[[time_stamp()]\] Has placed [target.name] ([target.ckey]) in disposals.")
+ target.attack_log += text("\[[time_stamp()]\] Has been placed in disposals by [user.name] ([user.ckey])")
+ msg_admin_attack("[user] ([user.ckey]) placed [target] ([target.ckey]) in a disposals unit. (JMP)")
+ else
+ return
+ if (target.client)
+ target.client.perspective = EYE_PERSPECTIVE
+ target.client.eye = src
+ target.loc = src
+
+ for (var/mob/C in viewers(src))
+ if(C == user)
+ continue
+ C.show_message(msg, 3)
+
+ update()
+ return
+
+// can breath normally in the disposal
+/obj/machinery/disposal/alter_health()
+ return get_turf(src)
+
+// attempt to move while inside
+/obj/machinery/disposal/relaymove(mob/user as mob)
+ if(user.stat || src.flushing)
+ return
+ src.go_out(user)
+ return
+
+// leave the disposal
+/obj/machinery/disposal/proc/go_out(mob/user)
+
+ if (user.client)
+ user.client.eye = user.client.mob
+ user.client.perspective = MOB_PERSPECTIVE
+ user.loc = src.loc
+ update()
+ return
+
+
+// monkeys can only pull the flush lever
+/obj/machinery/disposal/attack_paw(mob/user as mob)
+ if(stat & BROKEN)
+ return
+
+ flush = !flush
+ update()
+ return
+
+// ai as human but can't flush
+/obj/machinery/disposal/attack_ai(mob/user as mob)
+ interact(user, 1)
+
+// human interact with machine
+/obj/machinery/disposal/attack_hand(mob/user as mob)
+ if(user && user.loc == src)
+ usr << "\red You cannot reach the controls from inside."
+ return
+ /*
+ if(mode==-1)
+ usr << "\red The disposal units power is disabled."
+ return
+ */
+ interact(user, 0)
+
+// user interaction
+/obj/machinery/disposal/interact(mob/user, var/ai=0)
+
+ src.add_fingerprint(user)
+ if(stat & BROKEN)
+ user.unset_machine()
+ return
+
+ var/dat = "Waste Disposal UnitWaste Disposal Unit "
+
+ if(!ai) // AI can't pull flush handle
+ if(flush)
+ dat += "Disposal handle: Disengage Engaged"
else
- return ..(mover, target, height, air_group)
+ dat += "Disposal handle: Disengaged Engage"
+
+ dat += "
Eject contents "
+
+ if(mode <= 0)
+ dat += "Pump: Off On "
+ else if(mode == 1)
+ dat += "Pump: Off On (pressurizing) "
+ else
+ dat += "Pump: Off On (idle) "
+
+ var/per = 100* air_contents.return_pressure() / (SEND_PRESSURE)
+
+ dat += "Pressure: [round(per, 1)]% "
+
+
+ user.set_machine(src)
+ user << browse(dat, "window=disposal;size=360x170")
+ onclose(user, "disposal")
+
+// handle machine interaction
+
+/obj/machinery/disposal/Topic(href, href_list)
+ if(usr.loc == src)
+ usr << "\red You cannot reach the controls from inside."
+ return
+
+ if(mode==-1 && !href_list["eject"]) // only allow ejecting if mode is -1
+ usr << "\red The disposal units power is disabled."
+ return
+ ..()
+ src.add_fingerprint(usr)
+ if(stat & BROKEN)
+ return
+ if(usr.stat || usr.restrained() || src.flushing)
+ return
+
+ if (in_range(src, usr) && istype(src.loc, /turf))
+ usr.set_machine(src)
+
+ if(href_list["close"])
+ usr.unset_machine()
+ usr << browse(null, "window=disposal")
+ return
+
+ if(href_list["pump"])
+ if(text2num(href_list["pump"]))
+ mode = 1
+ else
+ mode = 0
+ update()
+
+ if(href_list["handle"])
+ flush = text2num(href_list["handle"])
+ update()
+
+ if(href_list["eject"])
+ eject()
+ else
+ usr << browse(null, "window=disposal")
+ usr.unset_machine()
+ return
+ return
+
+// eject the contents of the disposal unit
+/obj/machinery/disposal/proc/eject()
+ for(var/atom/movable/AM in src)
+ AM.loc = src.loc
+ AM.pipe_eject(0)
+ update()
+
+// update the icon & overlays to reflect mode & status
+/obj/machinery/disposal/proc/update()
+ overlays.Cut()
+ if(stat & BROKEN)
+ icon_state = "disposal-broken"
+ mode = 0
+ flush = 0
+ return
+
+ // flush handle
+ if(flush)
+ overlays += image('icons/obj/pipes/disposal.dmi', "dispover-handle")
+
+ // only handle is shown if no power
+ if(stat & NOPOWER || mode == -1)
+ return
+
+ // check for items in disposal - occupied light
+ if(contents.len > 0)
+ overlays += image('icons/obj/pipes/disposal.dmi', "dispover-full")
+
+ // charging and ready light
+ if(mode == 1)
+ overlays += image('icons/obj/pipes/disposal.dmi', "dispover-charge")
+ else if(mode == 2)
+ overlays += image('icons/obj/pipes/disposal.dmi', "dispover-ready")
+
+// timed process
+// charge the gas reservoir and perform flush if ready
+/obj/machinery/disposal/process()
+ if(!air_contents || (stat & BROKEN)) // nothing can happen if broken
+ update_use_power(0)
+ return
+
+ flush_count++
+ if( flush_count >= flush_every_ticks )
+ if( contents.len )
+ if(mode == 2)
+ spawn(0)
+ feedback_inc("disposal_auto_flush",1)
+ flush()
+ flush_count = 0
+
+ src.updateDialog()
+
+ if(flush && air_contents.return_pressure() >= SEND_PRESSURE ) // flush can happen even without power
+ flush()
+
+ if(mode != 1) // if off or ready, no need to charge
+ update_use_power(1)
+ return
+
+ // otherwise charge
+ src.pressurize()
+
+ // if full enough, switch to ready mode
+ if(air_contents.return_pressure() >= SEND_PRESSURE)
+ mode = 2
+ update()
+ return
+
+/obj/machinery/disposal/proc/pressurize()
+ if(stat & NOPOWER) // won't charge if no power
+ update_use_power(0)
+ return
+
+ var/atom/L = loc // recharging from loc turf
+ var/datum/gas_mixture/env = L.return_air()
+
+ var/power_draw = -1
+ if(env && env.temperature > 0)
+ var/transfer_moles = (PUMP_MAX_FLOW_RATE/env.volume)*env.total_moles //group_multiplier is divided out here
+ power_draw = pump_gas(src, env, air_contents, transfer_moles, active_power_usage)
+
+ if (power_draw < 0)
+ //update_use_power(0)
+ use_power = 1 //don't force update - easier on CPU
+ else
+ handle_power_draw(power_draw)
+
+// perform a flush
+/obj/machinery/disposal/proc/flush()
+
+ flushing = 1
+ flick("[icon_state]-flush", src)
+
+ var/wrapcheck = 0
+ var/obj/structure/disposalholder/H = new() // virtual holder object which actually
+ // travels through the pipes.
+ //Hacky test to get drones to mail themselves through disposals.
+ for(var/mob/living/silicon/robot/drone/D in src)
+ wrapcheck = 1
+
+ for(var/obj/item/smallDelivery/O in src)
+ wrapcheck = 1
+
+ if(wrapcheck == 1)
+ H.tomail = 1
+
+
+ air_contents = new() // new empty gas resv.
+
+ sleep(10)
+ if(last_sound < world.time + 1)
+ playsound(src, 'sound/machines/disposalflush.ogg', 50, 0, 0)
+ last_sound = world.time
+ sleep(5) // wait for animation to finish
+
+
+ H.init(src) // copy the contents of disposer to holder
+
+ H.start(src) // start the holder processing movement
+ flushing = 0
+ // now reset disposal state
+ flush = 0
+ if(mode == 2) // if was ready,
+ mode = 1 // switch to charging
+ update()
+ return
+
+
+// called when area power changes
+/obj/machinery/disposal/power_change()
+ ..() // do default setting/reset of stat NOPOWER bit
+ update() // update icon
+ return
+
+
+// called when holder is expelled from a disposal
+// should usually only occur if the pipe network is modified
+/obj/machinery/disposal/proc/expel(var/obj/structure/disposalholder/H)
+
+ var/turf/target
+ playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
+ if(H) // Somehow, someone managed to flush a window which broke mid-transit and caused the disposal to go in an infinite loop trying to expel null, hopefully this fixes it
+ for(var/atom/movable/AM in H)
+ target = get_offset_target_turf(src.loc, rand(5)-rand(5), rand(5)-rand(5))
+
+ AM.loc = src.loc
+ AM.pipe_eject(0)
+ if(!istype(AM,/mob/living/silicon/robot/drone)) //Poor drones kept smashing windows and taking system damage being fired out of disposals. ~Z
+ spawn(1)
+ if(AM)
+ AM.throw_at(target, 5, 1)
+
+ H.vent_gas(loc)
+ del(H)
+
+/obj/machinery/disposal/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
+ if (istype(mover,/obj/item) && mover.throwing)
+ var/obj/item/I = mover
+ if(istype(I, /obj/item/projectile))
+ return
+ if(prob(75))
+ I.loc = src
+ for(var/mob/M in viewers(src))
+ M.show_message("\the [I] lands in \the [src].", 3)
+ else
+ for(var/mob/M in viewers(src))
+ M.show_message("\the [I] bounces off of \the [src]'s rim!.", 3)
+ return 0
+ else
+ return ..(mover, target, height, air_group)
// virtual disposal object
// travels through pipes in lieu of actual items
@@ -746,10 +737,10 @@
// this will be revealed if a T-scanner is used
// if visible, use regular icon_state
proc/updateicon()
- if(invisibility)
+/* if(invisibility) //we hide things with alpha now, no need for transparent icons
icon_state = "[base_icon_state]f"
else
- icon_state = base_icon_state
+ icon_state = base_icon_state*/
return
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index 5f72368e..e77ef6d0 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -53,7 +53,7 @@
if(ishuman(user))
user.put_in_hands(wrapped)
else
- wrapped.loc = get_turf_loc(src)
+ wrapped.loc = get_turf(src)
del(src)
return
diff --git a/code/modules/research/circuitprinter.dm b/code/modules/research/circuitprinter.dm
index 0757e025..69ef2050 100644
--- a/code/modules/research/circuitprinter.dm
+++ b/code/modules/research/circuitprinter.dm
@@ -14,6 +14,10 @@ using metal and glass, it uses glass and reagents (usually sulfuric acis).
var/diamond_amount = 0
var/uranium_amount = 0
var/max_material_amount = 75000.0
+
+ use_power = 1
+ idle_power_usage = 30
+ active_power_usage = 2500
New()
..()
diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm
index 198ec3a1..2923343f 100644
--- a/code/modules/research/designs.dm
+++ b/code/modules/research/designs.dm
@@ -1290,6 +1290,15 @@ datum/design/mrspacman
materials = list("$glass" = 2000, "sacid" = 20)
build_path = "/obj/item/weapon/circuitboard/pacman/mrs"
+datum/design/batteryrack
+ name = "Cell rack PSU Board"
+ desc = "The circuit board for a power cell rack PSU."
+ id = "batteryrack"
+ req_tech = list("powerstorage" = 3, "engineering" = 2)
+ build_type = IMPRINTER
+ materials = list("$glass" = 2000, "sacid" = 20)
+ build_path = "/obj/item/weapon/circuitboard/batteryrack"
+
/////////////////////////////////////////
////////////Medical Tools////////////////
@@ -1836,8 +1845,7 @@ datum/design/security_hud
icon_state = "datadisk2"
item_state = "card-id"
w_class = 1.0
- m_amt = 30
- g_amt = 10
+ matter = list("metal" = 30, "glass" = 10)
var/datum/design/blueprint
/obj/item/weapon/disk/design_disk/New()
diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm
index 48105a55..a5642335 100644
--- a/code/modules/research/destructive_analyzer.dm
+++ b/code/modules/research/destructive_analyzer.dm
@@ -12,6 +12,10 @@ Note: Must be placed within 3 tiles of the R&D Console
icon_state = "d_analyzer"
var/obj/item/weapon/loaded_item = null
var/decon_mod = 1
+
+ use_power = 1
+ idle_power_usage = 30
+ active_power_usage = 2500
/obj/machinery/r_n_d/destructive_analyzer/New()
..()
diff --git a/code/modules/research/message_server.dm b/code/modules/research/message_server.dm
index e9692de8..2d7f723e 100644
--- a/code/modules/research/message_server.dm
+++ b/code/modules/research/message_server.dm
@@ -1,3 +1,6 @@
+#define MESSAGE_SERVER_SPAM_REJECT 1
+#define MESSAGE_SERVER_DEFAULT_SPAM_LIMIT 10
+
var/global/list/obj/machinery/message_server/message_servers = list()
/datum/data_pda_msg
@@ -59,6 +62,12 @@ var/global/list/obj/machinery/message_server/message_servers = list()
var/active = 1
var/decryptkey = "password"
+ //Spam filtering stuff
+ var/list/spamfilter = list("You have won", "your prize", "male enhancement", "shitcurity", \
+ "are happy to inform you", "account number", "enter your PIN")
+ //Messages having theese tokens will be rejected by server. Case sensitive
+ var/spamfilter_limit = MESSAGE_SERVER_DEFAULT_SPAM_LIMIT //Maximal amount of tokens
+
/obj/machinery/message_server/New()
message_servers += src
decryptkey = GenerateKey()
@@ -89,7 +98,13 @@ var/global/list/obj/machinery/message_server/message_servers = list()
return
/obj/machinery/message_server/proc/send_pda_message(var/recipient = "",var/sender = "",var/message = "")
+ var/result
+ for (var/token in spamfilter)
+ if (findtextEx(message,token))
+ message = "[message]" //Rejected messages will be indicated by red color.
+ result = token //Token caused rejection (if there are multiple, last will be chosen>.
pda_msgs += new/datum/data_pda_msg(recipient,sender,message)
+ return result
/obj/machinery/message_server/proc/send_rc_message(var/recipient = "",var/sender = "",var/message = "",var/stamp = "", var/id_auth = "", var/priority = 1)
rc_msgs += new/datum/data_rc_msg(recipient,sender,message,stamp,id_auth)
@@ -102,6 +117,16 @@ var/global/list/obj/machinery/message_server/message_servers = list()
return
+/obj/machinery/message_server/attackby(obj/item/weapon/O as obj, mob/living/user as mob)
+ if (active && !(stat & (BROKEN|NOPOWER)) && (spamfilter_limit < MESSAGE_SERVER_DEFAULT_SPAM_LIMIT*2) && \
+ istype(O,/obj/item/weapon/circuitboard/message_monitor))
+ spamfilter_limit += round(MESSAGE_SERVER_DEFAULT_SPAM_LIMIT / 2)
+ user.drop_item()
+ del(O)
+ user << "You install additional memory and processors into message server. Its filtering capabilities been enhanced."
+ else
+ ..(O, user)
+
/obj/machinery/message_server/update_icon()
if((stat & (BROKEN|NOPOWER)))
icon_state = "server-nopower"
diff --git a/code/modules/research/protolathe.dm b/code/modules/research/protolathe.dm
index bdf879d6..5773ecb6 100644
--- a/code/modules/research/protolathe.dm
+++ b/code/modules/research/protolathe.dm
@@ -11,6 +11,10 @@ Note: Must be placed west/left of and R&D console to function.
name = "Protolathe"
icon_state = "protolathe"
flags = OPENCONTAINER
+
+ use_power = 1
+ idle_power_usage = 30
+ active_power_usage = 5000
var/max_material_storage = 100000 //All this could probably be done better with a list but meh.
var/m_amount = 0.0
diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm
index 53aff8a2..f1822f9a 100644
--- a/code/modules/research/rdconsole.dm
+++ b/code/modules/research/rdconsole.dm
@@ -263,9 +263,9 @@ won't update every console in existence) but it's more of a hassle to do. Also,
files.UpdateTech(T, temp_tech[T])
if(linked_destroy.loaded_item.reliability < 100 && linked_destroy.loaded_item.crit_fail)
files.UpdateDesign(linked_destroy.loaded_item.type)
- if(linked_lathe) //Also sends salvaged materials to a linked protolathe, if any.
- linked_lathe.m_amount += min((linked_lathe.max_material_storage - linked_lathe.TotalMaterials()), (linked_destroy.loaded_item.m_amt*linked_destroy.decon_mod))
- linked_lathe.g_amount += min((linked_lathe.max_material_storage - linked_lathe.TotalMaterials()), (linked_destroy.loaded_item.g_amt*linked_destroy.decon_mod))
+ if(linked_lathe && linked_destroy.loaded_item.matter) //Also sends salvaged materials to a linked protolathe, if any.
+ linked_lathe.m_amount += min((linked_lathe.max_material_storage - linked_lathe.TotalMaterials()), (linked_destroy.loaded_item.matter["metal"]*linked_destroy.decon_mod))
+ linked_lathe.g_amount += min((linked_lathe.max_material_storage - linked_lathe.TotalMaterials()), (linked_destroy.loaded_item.matter["glass"]*linked_destroy.decon_mod))
linked_destroy.loaded_item = null
for(var/obj/I in linked_destroy.contents)
for(var/mob/M in I.contents)
@@ -282,7 +282,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
if(!(I in linked_destroy.component_parts))
del(I)
linked_destroy.icon_state = "d_analyzer"
- use_power(250)
+ use_power(linked_destroy.active_power_usage)
screen = 1.0
updateUsrDialog()
@@ -319,7 +319,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
files.RefreshResearch()
server_processed = 1
if(!istype(S, /obj/machinery/r_n_d/server/centcom) && server_processed)
- S.produce_heat(100)
+ S.produce_heat()
screen = 1.6
updateUsrDialog()
@@ -334,10 +334,10 @@ won't update every console in existence) but it's more of a hassle to do. Also,
being_built = D
break
if(being_built)
- var/power = 2000
+ var/power = linked_lathe.active_power_usage
for(var/M in being_built.materials)
power += round(being_built.materials[M] / 5)
- power = max(2000, power)
+ power = max(linked_lathe.active_power_usage, power)
screen = 0.3
linked_lathe.busy = 1
flick("protolathe_n",linked_lathe)
@@ -390,10 +390,10 @@ won't update every console in existence) but it's more of a hassle to do. Also,
being_built = D
break
if(being_built)
- var/power = 2000
+ var/power = linked_imprinter.active_power_usage
for(var/M in being_built.materials)
power += round(being_built.materials[M] / 5)
- power = max(2000, power)
+ power = max(linked_imprinter.active_power_usage, power)
screen = 0.4
linked_imprinter.busy = 1
flick("circuit_imprinter_ani",linked_imprinter)
diff --git a/code/modules/research/research.dm b/code/modules/research/research.dm
index 34913490..741d9050 100644
--- a/code/modules/research/research.dm
+++ b/code/modules/research/research.dm
@@ -263,8 +263,7 @@ datum/tech/robotics
icon_state = "datadisk2"
item_state = "card-id"
w_class = 1.0
- m_amt = 30
- g_amt = 10
+ matter = list("metal" = 30, "glass" = 10)
var/datum/tech/stored
/obj/item/weapon/disk/tech_disk/New()
diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm
index 8ab91623..7acb400b 100644
--- a/code/modules/research/server.dm
+++ b/code/modules/research/server.dm
@@ -9,8 +9,8 @@
var/id_with_upload_string = "" //String versions for easy editing in map editor.
var/id_with_download_string = ""
var/server_id = 0
- var/heat_gen = 100
- var/heating_power = 40000
+ var/produces_heat = 1
+ idle_power_usage = 800
var/delay = 10
req_access = list(access_rd) //Only the R&D can change server settings.
@@ -32,7 +32,7 @@
var/tot_rating = 0
for(var/obj/item/weapon/stock_parts/SP in src)
tot_rating += SP.rating
- heat_gen /= max(1, tot_rating)
+ idle_power_usage /= max(1, tot_rating)
/obj/machinery/r_n_d/server/initialize()
if(!files) files = new /datum/research(src)
@@ -67,7 +67,7 @@
if(delay)
delay--
else
- produce_heat(heat_gen)
+ produce_heat()
delay = initial(delay)
/obj/machinery/r_n_d/server/meteorhit(var/obj/O as obj)
@@ -100,25 +100,28 @@
C.files.AddDesign2Known(D)
C.files.RefreshResearch()
-/obj/machinery/r_n_d/server/proc/produce_heat(heat_amt)
- if(!(stat & (NOPOWER|BROKEN))) //Blatently stolen from space heater.
+/obj/machinery/r_n_d/server/proc/produce_heat()
+ if (!produces_heat)
+ return
+
+ if (!use_power)
+ return
+
+ if(!(stat & (NOPOWER|BROKEN))) //Blatently stolen from telecoms
var/turf/simulated/L = loc
if(istype(L))
var/datum/gas_mixture/env = L.return_air()
- if(env.temperature < (heat_amt+T0C))
- var/transfer_moles = 0.25 * env.total_moles()
+ var/transfer_moles = 0.25 * env.total_moles
- var/datum/gas_mixture/removed = env.remove(transfer_moles)
+ var/datum/gas_mixture/removed = env.remove(transfer_moles)
- if(removed)
+ if(removed)
+ var/heat_produced = idle_power_usage //obviously can't produce more heat than the machine draws from it's power source
+
+ removed.add_thermal_energy(heat_produced)
- var/heat_capacity = removed.heat_capacity()
- if(heat_capacity == 0 || heat_capacity == null)
- heat_capacity = 1
- removed.temperature = min((removed.temperature*heat_capacity + heating_power)/heat_capacity, 1000)
-
- env.merge(removed)
+ env.merge(removed)
/obj/machinery/r_n_d/server/attackby(var/obj/item/O as obj, var/mob/user as mob)
if (disabled)
diff --git a/code/modules/research/xenoarchaeology/artifact/artifact_unknown.dm b/code/modules/research/xenoarchaeology/artifact/artifact_unknown.dm
index a0762ff5..578f6cc1 100644
--- a/code/modules/research/xenoarchaeology/artifact/artifact_unknown.dm
+++ b/code/modules/research/xenoarchaeology/artifact/artifact_unknown.dm
@@ -143,13 +143,13 @@ var/list/valid_secondary_effect_types = list(\
else if(env.temperature > 375)
trigger_hot = 1
- if(env.toxins >= 10)
+ if(env.gas["toxins"] >= 10)
trigger_plasma = 1
- if(env.oxygen >= 10)
+ if(env.gas["toxins"] >= 10)
trigger_oxy = 1
- if(env.carbon_dioxide >= 10)
+ if(env.gas["carbon_dioxide"] >= 10)
trigger_co2 = 1
- if(env.nitrogen >= 10)
+ if(env.gas["nitrogen"] >= 10)
trigger_nitro = 1
//COLD ACTIVATION
diff --git a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasco2.dm b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasco2.dm
index c9cafa4e..c76ba927 100644
--- a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasco2.dm
+++ b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasco2.dm
@@ -15,12 +15,12 @@
/datum/artifact_effect/gasco2/DoEffectTouch(var/mob/user)
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env)
- env.carbon_dioxide += rand(2,15)
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("carbon_dioxide", rand(2, 15))
/datum/artifact_effect/gasco2/DoEffectAura()
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env && env.total_moles < max_pressure)
- env.carbon_dioxide += pick(0, 0, 0.1, rand())
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("carbon_dioxide", pick(0, 0, 0.1, rand()))
diff --git a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasnitro.dm b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasnitro.dm
index 527c2592..31ab6562 100644
--- a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasnitro.dm
+++ b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasnitro.dm
@@ -12,12 +12,12 @@
/datum/artifact_effect/gasnitro/DoEffectTouch(var/mob/user)
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env)
- env.nitrogen += rand(2,15)
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("nitrogen", rand(2, 15))
/datum/artifact_effect/gasnitro/DoEffectAura()
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env && env.total_moles < max_pressure)
- env.nitrogen += pick(0, 0, 0.1, rand())
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("nitrogen", pick(0, 0, 0.1, rand()))
diff --git a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasoxy.dm b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasoxy.dm
index d0daa695..888589d2 100644
--- a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasoxy.dm
+++ b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasoxy.dm
@@ -12,12 +12,12 @@
/datum/artifact_effect/gasoxy/DoEffectTouch(var/mob/user)
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env)
- env.oxygen += rand(2,15)
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("oxygen", rand(2, 15))
/datum/artifact_effect/gasoxy/DoEffectAura()
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env && env.total_moles < max_pressure)
- env.oxygen += pick(0, 0, 0.1, rand())
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("oxygen", pick(0, 0, 0.1, rand()))
diff --git a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasplasma.dm b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasplasma.dm
index c375e746..c4ee9c58 100644
--- a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasplasma.dm
+++ b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gasplasma.dm
@@ -12,12 +12,12 @@
/datum/artifact_effect/gasplasma/DoEffectTouch(var/mob/user)
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env)
- env.toxins += rand(2,15)
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("toxins", rand(2, 15)) //They had oxygen in the original. Surely they meant plasma?
/datum/artifact_effect/gasplasma/DoEffectAura()
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env && env.total_moles < max_pressure)
- env.toxins += pick(0, 0, 0.1, rand())
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("toxins", pick(0, 0, 0.1, rand()))
diff --git a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gassleeping.dm b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gassleeping.dm
index 5ce52567..420a31d6 100644
--- a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gassleeping.dm
+++ b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_gassleeping.dm
@@ -12,20 +12,12 @@
/datum/artifact_effect/gassleeping/DoEffectTouch(var/mob/user)
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env)
- var/datum/gas/sleeping_agent/trace_gas = new
- env.trace_gases += trace_gas
- trace_gas.moles = rand(2,15)
- env.update_values()
-
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("sleeping_agent", rand(2, 15))
/datum/artifact_effect/gassleeping/DoEffectAura()
if(holder)
- var/datum/gas_mixture/env = holder.loc.return_air()
- if(env && env.total_moles < max_pressure)
- var/datum/gas/sleeping_agent/trace_gas = new
- env.trace_gases += trace_gas
- trace_gas.moles = pick(0, 0, 0.1, rand())
- env.update_values()
-
+ var/turf/holder_loc = holder.loc
+ if(istype(holder_loc))
+ holder_loc.assume_gas("sleeping_agent", pick(0, 0, 0.1, rand()))
diff --git a/code/modules/research/xenoarchaeology/chemistry.dm b/code/modules/research/xenoarchaeology/chemistry.dm
index 13a8367b..e6c622a9 100644
--- a/code/modules/research/xenoarchaeology/chemistry.dm
+++ b/code/modules/research/xenoarchaeology/chemistry.dm
@@ -75,8 +75,7 @@ datum
desc = "A small, open-topped glass container for delicate research samples. It sports a re-useable strip for labelling with a pen."
icon = 'icons/obj/device.dmi'
icon_state = "solution_tray"
- m_amt = 0
- g_amt = 5
+ matter = list("glass" = 5)
w_class = 2.0
amount_per_transfer_from_this = 1
possible_transfer_amounts = list(1, 2)
diff --git a/code/modules/security levels/keycard authentication.dm b/code/modules/security levels/keycard authentication.dm
index febdc29e..29300f15 100644
--- a/code/modules/security levels/keycard authentication.dm
+++ b/code/modules/security levels/keycard authentication.dm
@@ -45,11 +45,9 @@
broadcast_request() //This is the device making the initial event request. It needs to broadcast to other devices
/obj/machinery/keycard_auth/power_change()
- if(powered(ENVIRON))
- stat &= ~NOPOWER
+ ..()
+ if(stat &NOPOWER)
icon_state = "auth_off"
- else
- stat |= NOPOWER
/obj/machinery/keycard_auth/attack_hand(mob/user as mob)
if(user.stat || stat & (NOPOWER|BROKEN))
@@ -71,7 +69,7 @@
dat += "Red alert"
if(!config.ert_admin_call_only)
dat += "Emergency Response Team"
-
+
dat += "Grant Emergency Maintenance Access"
dat += "Revoke Emergency Maintenance Access"
dat += ""
diff --git a/code/modules/shuttles/antagonist.dm b/code/modules/shuttles/antagonist.dm
new file mode 100644
index 00000000..c49c0990
--- /dev/null
+++ b/code/modules/shuttles/antagonist.dm
@@ -0,0 +1,9 @@
+/obj/machinery/computer/shuttle_control/multi/vox
+ name = "skipjack control console"
+ req_access = list(access_syndicate)
+ shuttle_tag = "Vox Skipjack"
+
+/obj/machinery/computer/shuttle_control/multi/syndicate
+ name = "Syndicate control console"
+ req_access = list(access_syndicate)
+ shuttle_tag = "Syndicate"
\ No newline at end of file
diff --git a/code/modules/shuttles/departmental.dm b/code/modules/shuttles/departmental.dm
new file mode 100644
index 00000000..9efd0ed7
--- /dev/null
+++ b/code/modules/shuttles/departmental.dm
@@ -0,0 +1,17 @@
+/obj/machinery/computer/shuttle_control/mining
+ name = "mining shuttle console"
+ shuttle_tag = "Mining"
+ //req_access = list(access_mining)
+ circuit = /obj/item/weapon/circuitboard/mining_shuttle
+
+/obj/machinery/computer/shuttle_control/engineering
+ name = "engineering shuttle console"
+ shuttle_tag = "Engineering"
+ //req_one_access_txt = "11;24"
+ circuit = /obj/item/weapon/circuitboard/engineering_shuttle
+
+/obj/machinery/computer/shuttle_control/research
+ name = "research shuttle console"
+ shuttle_tag = "Research"
+ //req_access = list(access_research)
+ circuit = /obj/item/weapon/circuitboard/research_shuttle
\ No newline at end of file
diff --git a/code/modules/shuttles/escape_pods.dm b/code/modules/shuttles/escape_pods.dm
new file mode 100644
index 00000000..719c29fd
--- /dev/null
+++ b/code/modules/shuttles/escape_pods.dm
@@ -0,0 +1,139 @@
+/datum/shuttle/ferry/escape_pod
+ var/datum/computer/file/embedded_program/docking/simple/escape_pod/arming_controller
+
+/datum/shuttle/ferry/escape_pod/can_launch()
+ if(arming_controller && !arming_controller.armed) //must be armed
+ return 0
+ if(location)
+ return 0 //it's a one-way trip.
+ return ..()
+
+/datum/shuttle/ferry/escape_pod/can_force()
+ if (arming_controller.eject_time && world.time < arming_controller.eject_time + 50)
+ return 0 //dont allow force launching until 5 seconds after the arming controller has reached it's countdown
+ return ..()
+
+/datum/shuttle/ferry/escape_pod/can_cancel()
+ return 0
+
+
+//This controller goes on the escape pod itself
+/obj/machinery/embedded_controller/radio/simple_docking_controller/escape_pod
+ name = "escape pod controller"
+ var/datum/shuttle/ferry/escape_pod/pod
+
+/obj/machinery/embedded_controller/radio/simple_docking_controller/escape_pod/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+ var/data[0]
+
+ data = list(
+ "docking_status" = docking_program.get_docking_status(),
+ "override_enabled" = docking_program.override_enabled,
+ "door_state" = docking_program.memory["door_status"]["state"],
+ "door_lock" = docking_program.memory["door_status"]["lock"],
+ "can_force" = pod.can_force() || (emergency_shuttle.departed && pod.can_launch()), //allow players to manually launch ahead of time if the shuttle leaves
+ "is_armed" = pod.arming_controller.armed,
+ )
+
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
+
+ if (!ui)
+ ui = new(user, src, ui_key, "escape_pod_console.tmpl", name, 470, 290)
+ ui.set_initial_data(data)
+ ui.open()
+ ui.set_auto_update(1)
+
+/obj/machinery/embedded_controller/radio/simple_docking_controller/escape_pod/Topic(href, href_list)
+ if(..()) //I hate this "return 1 to indicate they are not allowed to use the controller" crap, but not sure how else to do it without being able to call machinery/Topic() directly.
+ return 1
+
+ if("manual_arm")
+ pod.arming_controller.arm()
+ if("force_launch")
+ if (pod.can_force())
+ pod.force_launch(src)
+ else if (emergency_shuttle.departed && pod.can_launch()) //allow players to manually launch ahead of time if the shuttle leaves
+ pod.launch(src)
+
+ return 0
+
+
+
+//This controller is for the escape pod berth (station side)
+/obj/machinery/embedded_controller/radio/simple_docking_controller/escape_pod_berth
+ name = "escape pod berth controller"
+
+/obj/machinery/embedded_controller/radio/simple_docking_controller/escape_pod_berth/initialize()
+ ..()
+ docking_program = new/datum/computer/file/embedded_program/docking/simple/escape_pod(src)
+ program = docking_program
+
+/obj/machinery/embedded_controller/radio/simple_docking_controller/escape_pod_berth/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+ var/data[0]
+
+ var/armed = null
+ if (istype(docking_program, /datum/computer/file/embedded_program/docking/simple/escape_pod))
+ var/datum/computer/file/embedded_program/docking/simple/escape_pod/P = docking_program
+ armed = P.armed
+
+ data = list(
+ "docking_status" = docking_program.get_docking_status(),
+ "override_enabled" = docking_program.override_enabled,
+ "armed" = armed,
+ )
+
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
+
+ if (!ui)
+ ui = new(user, src, ui_key, "escape_pod_berth_console.tmpl", name, 470, 290)
+ ui.set_initial_data(data)
+ ui.open()
+ ui.set_auto_update(1)
+
+/obj/machinery/embedded_controller/radio/simple_docking_controller/escape_pod_berth/attackby(obj/item/weapon/W as obj, mob/user as mob)
+ if (istype(W, /obj/item/weapon/card/emag) && !emagged)
+ user << "\blue You emag the [src], arming the escape pod!"
+ emagged = 1
+ if (istype(docking_program, /datum/computer/file/embedded_program/docking/simple/escape_pod))
+ var/datum/computer/file/embedded_program/docking/simple/escape_pod/P = docking_program
+ if (!P.armed)
+ P.arm()
+ return
+
+ ..()
+
+
+
+//A docking controller program for a simple door based docking port
+/datum/computer/file/embedded_program/docking/simple/escape_pod
+ var/armed = 0
+ var/eject_delay = 10 //give latecomers some time to get out of the way if they don't make it onto the pod
+ var/eject_time = null
+ var/closing = 0
+
+/datum/computer/file/embedded_program/docking/simple/escape_pod/proc/arm()
+ armed = 1
+ open_door()
+
+
+/datum/computer/file/embedded_program/docking/simple/escape_pod/receive_user_command(command)
+ if (!armed)
+ return
+ ..(command)
+
+/datum/computer/file/embedded_program/docking/simple/escape_pod/process()
+ ..()
+ if (eject_time && world.time >= eject_time && !closing)
+ close_door()
+ closing = 1
+
+/datum/computer/file/embedded_program/docking/simple/escape_pod/prepare_for_docking()
+ return
+
+/datum/computer/file/embedded_program/docking/simple/escape_pod/ready_for_docking()
+ return 1
+
+/datum/computer/file/embedded_program/docking/simple/escape_pod/finish_docking()
+ return //don't do anything - the doors only open when the pod is armed.
+
+/datum/computer/file/embedded_program/docking/simple/escape_pod/prepare_for_undocking()
+ eject_time = world.time + eject_delay*10
diff --git a/code/modules/shuttles/shuttle.dm b/code/modules/shuttles/shuttle.dm
new file mode 100644
index 00000000..27e42312
--- /dev/null
+++ b/code/modules/shuttles/shuttle.dm
@@ -0,0 +1,128 @@
+//These lists are populated in /datum/shuttle_controller/New()
+//Shuttle controller is instantiated in master_controller.dm.
+
+//shuttle moving state defines are in setup.dm
+
+/datum/shuttle
+ var/warmup_time = 0
+ var/moving_status = SHUTTLE_IDLE
+
+ var/docking_controller_tag //tag of the controller used to coordinate docking
+ var/datum/computer/file/embedded_program/docking/docking_controller //the controller itself. (micro-controller, not game controller)
+
+ var/arrive_time = 0 //the time at which the shuttle arrives when long jumping
+
+/datum/shuttle/proc/short_jump(var/area/origin,var/area/destination)
+ if(moving_status != SHUTTLE_IDLE) return
+
+ //it would be cool to play a sound here
+ moving_status = SHUTTLE_WARMUP
+ spawn(warmup_time*10)
+ if (moving_status == SHUTTLE_IDLE)
+ return //someone cancelled the launch
+
+ move(origin, destination)
+ moving_status = SHUTTLE_IDLE
+
+/datum/shuttle/proc/long_jump(var/area/departing, var/area/destination, var/area/interim, var/travel_time, var/direction)
+ //world << "shuttle/long_jump: departing=[departing], destination=[destination], interim=[interim], travel_time=[travel_time]"
+ if(moving_status != SHUTTLE_IDLE) return
+
+ //it would be cool to play a sound here
+ moving_status = SHUTTLE_WARMUP
+ spawn(warmup_time*10)
+ if (moving_status == SHUTTLE_IDLE)
+ return //someone cancelled the launch
+
+ arrive_time = world.time + travel_time*10
+ moving_status = SHUTTLE_INTRANSIT
+ move(departing, interim, direction)
+
+
+ while (world.time < arrive_time)
+ sleep(5)
+
+ move(interim, destination, direction)
+ moving_status = SHUTTLE_IDLE
+
+/datum/shuttle/proc/dock()
+ if (!docking_controller)
+ return
+
+ var/dock_target = current_dock_target()
+ if (!dock_target)
+ return
+
+ docking_controller.initiate_docking(dock_target)
+
+/datum/shuttle/proc/undock()
+ if (!docking_controller)
+ return
+ docking_controller.initiate_undocking()
+
+/datum/shuttle/proc/current_dock_target()
+ return null
+
+/datum/shuttle/proc/skip_docking_checks()
+ if (!docking_controller || !current_dock_target())
+ return 1 //shuttles without docking controllers or at locations without docking ports act like old-style shuttles
+ return 0
+
+//just moves the shuttle from A to B, if it can be moved
+//A note to anyone overriding move in a subtype. move() must absolutely not, under any circumstances, fail to move the shuttle.
+//If you want to conditionally cancel shuttle launches, that logic must go in short_jump() or long_jump()
+/datum/shuttle/proc/move(var/area/origin, var/area/destination, var/direction=null)
+
+ //world << "move_shuttle() called for [shuttle_tag] leaving [origin] en route to [destination]."
+
+ //world << "area_coming_from: [origin]"
+ //world << "destination: [destination]"
+
+ if(origin == destination)
+ //world << "cancelling move, shuttle will overlap."
+ return
+
+ if (docking_controller && !docking_controller.undocked())
+ docking_controller.force_undock()
+
+ var/list/dstturfs = list()
+ var/throwy = world.maxy
+
+ for(var/turf/T in destination)
+ dstturfs += T
+ if(T.y < throwy)
+ throwy = T.y
+
+ for(var/turf/T in dstturfs)
+ var/turf/D = locate(T.x, throwy - 1, 1)
+ for(var/atom/movable/AM as mob|obj in T)
+ AM.Move(D)
+ if(istype(T, /turf/simulated))
+ del(T)
+
+ for(var/mob/living/carbon/bug in destination)
+ bug.gib()
+
+ for(var/mob/living/simple_animal/pest in destination)
+ pest.gib()
+
+ origin.move_contents_to(destination, direction=direction)
+
+ for(var/mob/M in destination)
+ if(M.client)
+ spawn(0)
+ if(M.buckled)
+ M << "\red Sudden acceleration presses you into your chair!"
+ shake_camera(M, 3, 1)
+ else
+ M << "\red The floor lurches beneath you!"
+ shake_camera(M, 10, 1)
+ if(istype(M, /mob/living/carbon))
+ if(!M.buckled)
+ M.Weaken(3)
+
+ return
+
+//returns 1 if the shuttle has a valid arrive time
+/datum/shuttle/proc/has_arrive_time()
+ return (moving_status == SHUTTLE_INTRANSIT)
\ No newline at end of file
diff --git a/code/modules/shuttles/shuttle_console.dm b/code/modules/shuttles/shuttle_console.dm
new file mode 100644
index 00000000..a5b08a0b
--- /dev/null
+++ b/code/modules/shuttles/shuttle_console.dm
@@ -0,0 +1,97 @@
+/obj/machinery/computer/shuttle_control
+ name = "shuttle control console"
+ icon = 'icons/obj/computer.dmi'
+ icon_state = "shuttle"
+ circuit = null
+
+ var/shuttle_tag // Used to coordinate data in shuttle controller.
+ var/hacked = 0 // Has been emagged, no access restrictions.
+
+
+/obj/machinery/computer/shuttle_control/attack_hand(user as mob)
+ if(..(user))
+ return
+ //src.add_fingerprint(user) //shouldn't need fingerprints just for looking at it.
+ if(!allowed(user))
+ user << "\red Access Denied."
+ return 1
+
+ ui_interact(user)
+
+/obj/machinery/computer/shuttle_control/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+ var/data[0]
+ var/datum/shuttle/ferry/shuttle = shuttle_controller.shuttles[shuttle_tag]
+ if (!istype(shuttle))
+ return
+
+ var/shuttle_state
+ switch(shuttle.moving_status)
+ if(SHUTTLE_IDLE) shuttle_state = "idle"
+ if(SHUTTLE_WARMUP) shuttle_state = "warmup"
+ if(SHUTTLE_INTRANSIT) shuttle_state = "in_transit"
+
+ var/shuttle_status
+ switch (shuttle.process_state)
+ if(IDLE_STATE)
+ if (shuttle.in_use)
+ shuttle_status = "Busy."
+ else if (!shuttle.location)
+ shuttle_status = "Standing-by at station."
+ else
+ shuttle_status = "Standing-by at offsite location."
+ if(WAIT_LAUNCH)
+ shuttle_status = "Shuttle has recieved command and will depart shortly."
+ if(WAIT_ARRIVE)
+ shuttle_status = "Proceeding to destination."
+ if(WAIT_FINISH)
+ shuttle_status = "Arriving at destination now."
+
+ data = list(
+ "shuttle_status" = shuttle_status,
+ "shuttle_state" = shuttle_state,
+ "has_docking" = shuttle.docking_controller? 1 : 0,
+ "docking_status" = shuttle.docking_controller? shuttle.docking_controller.get_docking_status() : null,
+ "docking_override" = shuttle.docking_controller? shuttle.docking_controller.override_enabled : null,
+ "can_launch" = shuttle.can_launch(),
+ "can_cancel" = shuttle.can_cancel(),
+ "can_force" = shuttle.can_force(),
+ )
+
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
+
+ if (!ui)
+ ui = new(user, src, ui_key, "shuttle_control_console.tmpl", "[shuttle_tag] Shuttle Control", 470, 310)
+ ui.set_initial_data(data)
+ ui.open()
+ ui.set_auto_update(1)
+
+/obj/machinery/computer/shuttle_control/Topic(href, href_list)
+ if(..())
+ return
+
+ usr.set_machine(src)
+ src.add_fingerprint(usr)
+
+ var/datum/shuttle/ferry/shuttle = shuttle_controller.shuttles[shuttle_tag]
+ if (!istype(shuttle))
+ return
+
+ if(href_list["move"])
+ shuttle.launch(src)
+ if(href_list["force"])
+ shuttle.force_launch(src)
+ else if(href_list["cancel"])
+ shuttle.cancel_launch(src)
+
+/obj/machinery/computer/shuttle_control/attackby(obj/item/weapon/W as obj, mob/user as mob)
+
+ if (istype(W, /obj/item/weapon/card/emag))
+ src.req_access = list()
+ src.req_one_access = list()
+ hacked = 1
+ usr << "You short out the console's ID checking system. It's now available to everyone!"
+ else
+ ..()
+
+/obj/machinery/computer/shuttle_control/bullet_act(var/obj/item/projectile/Proj)
+ visible_message("[Proj] ricochets off [src]!")
diff --git a/code/modules/shuttles/shuttle_emergency.dm b/code/modules/shuttles/shuttle_emergency.dm
new file mode 100644
index 00000000..daa783e4
--- /dev/null
+++ b/code/modules/shuttles/shuttle_emergency.dm
@@ -0,0 +1,236 @@
+
+/datum/shuttle/ferry/emergency
+ //pass
+
+/datum/shuttle/ferry/emergency/arrived()
+ if (istype(in_use, /obj/machinery/computer/shuttle_control/emergency))
+ var/obj/machinery/computer/shuttle_control/emergency/C = in_use
+ C.reset_authorization()
+
+ emergency_shuttle.shuttle_arrived()
+
+/datum/shuttle/ferry/emergency/long_jump(var/area/departing, var/area/destination, var/area/interim, var/travel_time, var/direction)
+ //world << "shuttle/ferry/emergency/long_jump: departing=[departing], destination=[destination], interim=[interim], travel_time=[travel_time]"
+ if (!location)
+ travel_time = SHUTTLE_TRANSIT_DURATION_RETURN
+ else
+ travel_time = SHUTTLE_TRANSIT_DURATION
+
+ //update move_time and launch_time so we get correct ETAs
+ move_time = travel_time
+ emergency_shuttle.launch_time = world.time
+
+ ..()
+
+/datum/shuttle/ferry/emergency/move(var/area/origin,var/area/destination)
+ ..(origin, destination)
+
+ if (origin == area_station) //leaving the station
+ emergency_shuttle.departed = 1
+
+ if (emergency_shuttle.evac)
+ captain_announce("The Emergency Shuttle has left the station. Estimate [round(emergency_shuttle.estimate_arrival_time()/60,1)] minutes until the shuttle docks at Central Command.")
+ else
+ captain_announce("The Crew Transfer Shuttle has left the station. Estimate [round(emergency_shuttle.estimate_arrival_time()/60,1)] minutes until the shuttle docks at Central Command.")
+
+/datum/shuttle/ferry/emergency/can_launch(var/user)
+ if (istype(user, /obj/machinery/computer/shuttle_control/emergency))
+ var/obj/machinery/computer/shuttle_control/emergency/C = user
+ if (!C.has_authorization())
+ return 0
+ return ..()
+
+/datum/shuttle/ferry/emergency/can_force(var/user)
+ if (istype(user, /obj/machinery/computer/shuttle_control/emergency))
+ var/obj/machinery/computer/shuttle_control/emergency/C = user
+
+ //initiating or cancelling a launch ALWAYS requires authorization, but if we are already set to launch anyways than forcing does not.
+ //this is so that people can force launch if the docking controller cannot safely undock without needing X heads to swipe.
+ if (process_state != WAIT_LAUNCH && !C.has_authorization())
+ return 0
+ return ..()
+
+/datum/shuttle/ferry/emergency/can_cancel(var/user)
+ if (istype(user, /obj/machinery/computer/shuttle_control/emergency))
+ var/obj/machinery/computer/shuttle_control/emergency/C = user
+ if (!C.has_authorization())
+ return 0
+ return ..()
+
+/datum/shuttle/ferry/emergency/launch(var/user)
+ if (!can_launch(user)) return
+
+ if (istype(user, /obj/machinery/computer/shuttle_control/emergency)) //if we were given a command by an emergency shuttle console
+ if (emergency_shuttle.autopilot)
+ emergency_shuttle.autopilot = 0
+ world << "\blue Alert: The shuttle autopilot has been overridden. Launch sequence initiated!"
+
+ ..(user)
+
+/datum/shuttle/ferry/emergency/force_launch(var/user)
+ if (!can_force(user)) return
+
+ if (istype(user, /obj/machinery/computer/shuttle_control/emergency)) //if we were given a command by an emergency shuttle console
+ if (emergency_shuttle.autopilot)
+ emergency_shuttle.autopilot = 0
+ world << "\blue Alert: The shuttle autopilot has been overridden. Bluespace drive engaged!"
+
+ ..(user)
+
+/datum/shuttle/ferry/emergency/cancel_launch(var/user)
+ if (!can_cancel(user)) return
+
+ if (istype(user, /obj/machinery/computer/shuttle_control/emergency)) //if we were given a command by an emergency shuttle console
+ if (emergency_shuttle.autopilot)
+ emergency_shuttle.autopilot = 0
+ world << "\blue Alert: The shuttle autopilot has been overridden. Launch sequence aborted!"
+
+ ..(user)
+
+
+
+/obj/machinery/computer/shuttle_control/emergency
+ shuttle_tag = "Escape"
+ var/debug = 0
+ var/req_authorizations = 2
+ var/list/authorized = list()
+
+/obj/machinery/computer/shuttle_control/emergency/proc/has_authorization()
+ return (authorized.len >= req_authorizations || emagged)
+
+/obj/machinery/computer/shuttle_control/emergency/proc/reset_authorization()
+ //no need to reset emagged status. If they really want to go back to the station they can.
+ authorized = initial(authorized)
+
+//returns 1 if the ID was accepted and a new authorization was added, 0 otherwise
+/obj/machinery/computer/shuttle_control/emergency/proc/read_authorization(var/ident)
+ if (!ident)
+ return 0
+ if (authorized.len >= req_authorizations)
+ return 0 //don't need any more
+
+ var/list/access
+ var/auth_name
+ var/dna_hash
+
+ if(istype(ident, /obj/item/weapon/card/id))
+ var/obj/item/weapon/card/id/ID = ident
+ access = ID.access
+ auth_name = "[ID.registered_name] ([ID.assignment])"
+ dna_hash = ID.dna_hash
+
+ if(istype(ident, /obj/item/device/pda))
+ var/obj/item/device/pda/PDA = ident
+ access = PDA.id.access
+ auth_name = "[PDA.id.registered_name] ([PDA.id.assignment])"
+ dna_hash = PDA.id.dna_hash
+
+ if (!access || !istype(access))
+ return 0 //not an ID
+
+ if (dna_hash in authorized)
+ src.visible_message("[src] buzzes. That ID has already been scanned.")
+ return 0
+
+ if (!(access_heads in access))
+ src.visible_message("[src] buzzes, rejecting [ident].")
+ return 0
+
+ src.visible_message("[src] beeps as it scans [ident].")
+ authorized[dna_hash] = auth_name
+ if (req_authorizations - authorized.len)
+ world << "\blue Alert: [req_authorizations - authorized.len] authorization\s needed to override the shuttle autopilot."
+ return 1
+
+
+
+
+/obj/machinery/computer/shuttle_control/emergency/attackby(obj/item/weapon/W as obj, mob/user as mob)
+ if (istype(W, /obj/item/weapon/card/emag) && !emagged)
+ user << "\blue You short out the [src]'s authorization protocols."
+ emagged = 1
+ return
+
+ read_authorization(W)
+ ..()
+
+/obj/machinery/computer/shuttle_control/emergency/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+ var/data[0]
+ var/datum/shuttle/ferry/emergency/shuttle = shuttle_controller.shuttles[shuttle_tag]
+ if (!istype(shuttle))
+ return
+
+ var/shuttle_state
+ switch(shuttle.moving_status)
+ if(SHUTTLE_IDLE) shuttle_state = "idle"
+ if(SHUTTLE_WARMUP) shuttle_state = "warmup"
+ if(SHUTTLE_INTRANSIT) shuttle_state = "in_transit"
+
+ var/shuttle_status
+ switch (shuttle.process_state)
+ if(IDLE_STATE)
+ if (shuttle.in_use)
+ shuttle_status = "Busy."
+ else if (!shuttle.location)
+ shuttle_status = "Standing-by at [station_name]."
+ else
+ shuttle_status = "Standing-by at Central Command."
+ if(WAIT_LAUNCH)
+ shuttle_status = "Shuttle has recieved command and will depart shortly."
+ if(WAIT_ARRIVE)
+ shuttle_status = "Proceeding to destination."
+ if(WAIT_FINISH)
+ shuttle_status = "Arriving at destination now."
+
+ //build a list of authorizations
+ var/list/auth_list[req_authorizations]
+
+ if (!emagged)
+ var/i = 1
+ for (var/dna_hash in authorized)
+ auth_list[i++] = list("auth_name"=authorized[dna_hash], "auth_hash"=dna_hash)
+
+ while (i <= req_authorizations) //fill up the rest of the list with blank entries
+ auth_list[i++] = list("auth_name"="", "auth_hash"=null)
+ else
+ for (var/i = 1; i <= req_authorizations; i++)
+ auth_list[i] = list("auth_name"="ERROR", "auth_hash"=null)
+
+ var/has_auth = has_authorization()
+
+ data = list(
+ "shuttle_status" = shuttle_status,
+ "shuttle_state" = shuttle_state,
+ "has_docking" = shuttle.docking_controller? 1 : 0,
+ "docking_status" = shuttle.docking_controller? shuttle.docking_controller.get_docking_status() : null,
+ "docking_override" = shuttle.docking_controller? shuttle.docking_controller.override_enabled : null,
+ "can_launch" = shuttle.can_launch(src),
+ "can_cancel" = shuttle.can_cancel(src),
+ "can_force" = shuttle.can_force(src),
+ "auth_list" = auth_list,
+ "has_auth" = has_auth,
+ "user" = debug? user : null,
+ )
+
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
+
+ if (!ui)
+ ui = new(user, src, ui_key, "escape_shuttle_control_console.tmpl", "Shuttle Control", 470, 420)
+ ui.set_initial_data(data)
+ ui.open()
+ ui.set_auto_update(1)
+
+/obj/machinery/computer/shuttle_control/emergency/Topic(href, href_list)
+ if(..())
+ return
+
+ if(href_list["removeid"])
+ var/dna_hash = href_list["removeid"]
+ authorized -= dna_hash
+
+ if(!emagged && href_list["scanid"])
+ //They selected an empty entry. Try to scan their id.
+ if (ishuman(usr))
+ var/mob/living/carbon/human/H = usr
+ if (!read_authorization(H.get_active_hand())) //try to read what's in their hand first
+ read_authorization(H.wear_id)
diff --git a/code/modules/shuttles/shuttle_ferry.dm b/code/modules/shuttles/shuttle_ferry.dm
new file mode 100644
index 00000000..8d6d1594
--- /dev/null
+++ b/code/modules/shuttles/shuttle_ferry.dm
@@ -0,0 +1,165 @@
+#define IDLE_STATE 0
+#define WAIT_LAUNCH 1
+#define WAIT_ARRIVE 2
+#define WAIT_FINISH 3
+
+#define DOCK_ATTEMPT_TIMEOUT 200 //how long in ticks we wait before assuming the docking controller is broken or blown up.
+
+/datum/shuttle/ferry
+ var/location = 0 //0 = at area_station, 1 = at area_offsite
+ var/direction = 0 //0 = going to station, 1 = going to offsite.
+ var/process_state = IDLE_STATE
+
+ var/in_use = null //tells the controller whether this shuttle needs processing
+
+ var/area_transition
+ var/move_time = 0 //the time spent in the transition area
+ var/transit_direction = null //needed for area/move_contents_to() to properly handle shuttle corners - not exactly sure how it works.
+
+ var/area_station
+ var/area_offsite
+ //TODO: change location to a string and use a mapping for area and dock targets.
+ var/dock_target_station
+ var/dock_target_offsite
+
+ var/last_dock_attempt_time = 0
+
+/datum/shuttle/ferry/short_jump(var/area/origin,var/area/destination)
+ if(isnull(location))
+ return
+
+ if(!destination)
+ destination = get_location_area(!location)
+ if(!origin)
+ origin = get_location_area(location)
+
+ direction = !location
+ ..(origin, destination)
+
+/datum/shuttle/ferry/long_jump(var/area/departing, var/area/destination, var/area/interim, var/travel_time, var/direction)
+ //world << "shuttle/ferry/long_jump: departing=[departing], destination=[destination], interim=[interim], travel_time=[travel_time]"
+ if(isnull(location))
+ return
+
+ if(!destination)
+ destination = get_location_area(!location)
+ if(!departing)
+ departing = get_location_area(location)
+
+ direction = !location
+ ..(departing, destination, interim, travel_time, direction)
+
+/datum/shuttle/ferry/move(var/area/origin,var/area/destination)
+ ..(origin, destination)
+
+ if (destination == area_station) location = 0
+ if (destination == area_offsite) location = 1
+ //if this is a long_jump retain the location we were last at until we get to the new one
+
+/datum/shuttle/ferry/dock()
+ ..()
+ last_dock_attempt_time = world.time
+
+/datum/shuttle/ferry/proc/get_location_area(location_id = null)
+ if (isnull(location_id))
+ location_id = location
+
+ if (!location_id)
+ return area_station
+ return area_offsite
+
+/datum/shuttle/ferry/proc/process()
+ switch(process_state)
+ if (WAIT_LAUNCH)
+ if (skip_docking_checks() || docking_controller.can_launch())
+
+ //world << "shuttle/ferry/process: area_transition=[area_transition], travel_time=[travel_time]"
+ if (move_time && area_transition)
+ long_jump(interim=area_transition, travel_time=move_time, direction=transit_direction)
+ else
+ short_jump()
+
+ process_state = WAIT_ARRIVE
+ if (WAIT_ARRIVE)
+ if (moving_status == SHUTTLE_IDLE)
+ dock()
+ in_use = null //release lock
+ process_state = WAIT_FINISH
+ if (WAIT_FINISH)
+ if (skip_docking_checks() || docking_controller.docked() || world.time > last_dock_attempt_time + DOCK_ATTEMPT_TIMEOUT)
+ process_state = IDLE_STATE
+ arrived()
+
+/datum/shuttle/ferry/current_dock_target()
+ var/dock_target
+ if (!location) //station
+ dock_target = dock_target_station
+ else
+ dock_target = dock_target_offsite
+ return dock_target
+
+
+/datum/shuttle/ferry/proc/launch(var/user)
+ if (!can_launch()) return
+
+ in_use = user //obtain an exclusive lock on the shuttle
+
+ process_state = WAIT_LAUNCH
+ undock()
+
+/datum/shuttle/ferry/proc/force_launch(var/user)
+ if (!can_force()) return
+
+ in_use = user //obtain an exclusive lock on the shuttle
+
+ if (move_time && area_transition)
+ long_jump(interim=area_transition, travel_time=move_time, direction=transit_direction)
+ else
+ short_jump()
+
+
+ process_state = WAIT_ARRIVE
+
+/datum/shuttle/ferry/proc/cancel_launch(var/user)
+ if (!can_cancel()) return
+
+ moving_status = SHUTTLE_IDLE
+ process_state = WAIT_FINISH
+ in_use = null
+
+ if (docking_controller && !docking_controller.undocked())
+ docking_controller.force_undock()
+
+ spawn(10)
+ dock()
+
+ return
+
+/datum/shuttle/ferry/proc/can_launch()
+ if (moving_status != SHUTTLE_IDLE)
+ return 0
+
+ if (in_use)
+ return 0
+
+ return 1
+
+/datum/shuttle/ferry/proc/can_force()
+ if (moving_status == SHUTTLE_IDLE && process_state == WAIT_LAUNCH)
+ return 1
+ return 0
+
+/datum/shuttle/ferry/proc/can_cancel()
+ if (moving_status == SHUTTLE_WARMUP || process_state == WAIT_LAUNCH)
+ return 1
+ return 0
+
+//returns 1 if the shuttle is getting ready to move, but is not in transit yet
+/datum/shuttle/ferry/proc/is_launching()
+ return (moving_status == SHUTTLE_WARMUP || process_state == WAIT_LAUNCH)
+
+//This gets called when the shuttle finishes arriving at it's destination
+//This can be used by subtypes to do things when the shuttle arrives.
+/datum/shuttle/ferry/proc/arrived()
+ return //do nothing for now
+
diff --git a/code/modules/shuttles/shuttle_specops.dm b/code/modules/shuttles/shuttle_specops.dm
new file mode 100644
index 00000000..9bad8d0c
--- /dev/null
+++ b/code/modules/shuttles/shuttle_specops.dm
@@ -0,0 +1,202 @@
+/obj/machinery/computer/shuttle_control/specops
+ name = "special operations shuttle console"
+ shuttle_tag = "Special Operations"
+ req_access = list(access_cent_specops)
+
+//for shuttles that may use a different docking port at each location
+/datum/shuttle/ferry/multidock
+ var/docking_controller_tag_station
+ var/docking_controller_tag_offsite
+ var/datum/computer/file/embedded_program/docking/docking_controller_station
+ var/datum/computer/file/embedded_program/docking/docking_controller_offsite
+
+/datum/shuttle/ferry/multidock/move(var/area/origin,var/area/destination)
+ ..(origin, destination)
+ if (!location)
+ docking_controller = docking_controller_station
+ else
+ docking_controller = docking_controller_offsite
+
+/datum/shuttle/ferry/multidock/specops
+ var/specops_return_delay = 6000 //After moving, the amount of time that must pass before the shuttle may move again
+ var/specops_countdown_time = 600 //Length of the countdown when moving the shuttle
+
+ var/obj/item/device/radio/intercom/announcer = null
+ var/reset_time = 0 //the world.time at which the shuttle will be ready to move again.
+ var/launch_prep = 0
+ var/cancel_countdown = 0
+
+/datum/shuttle/ferry/multidock/specops/New()
+ ..()
+ announcer = new /obj/item/device/radio/intercom(null)//We need a fake AI to announce some stuff below. Otherwise it will be wonky.
+ announcer.config(list("Response Team" = 0))
+
+/datum/shuttle/ferry/multidock/specops/proc/radio_announce(var/message)
+ if(announcer)
+ announcer.autosay(message, "A.L.I.C.E.", "Response Team")
+
+/datum/shuttle/ferry/multidock/specops/launch(var/user)
+ if (!can_launch())
+ return
+
+ if (istype(user, /obj/machinery/computer))
+ var/obj/machinery/computer/C = user
+
+ if(world.time <= reset_time)
+ C.visible_message("\blue Central Command will not allow the Special Operations shuttle to launch yet.")
+ if (((world.time - reset_time)/10) > 60)
+ C.visible_message("\blue [-((world.time - reset_time)/10)/60] minutes remain!")
+ else
+ C.visible_message("\blue [-(world.time - reset_time)/10] seconds remain!")
+ return
+
+ C.visible_message("\blue The Special Operations shuttle will depart in [(specops_countdown_time/10)] seconds.")
+
+ if (location) //returning
+ radio_announce("THE SPECIAL OPERATIONS SHUTTLE IS PREPARING TO RETURN")
+ else
+ radio_announce("THE SPECIAL OPERATIONS SHUTTLE IS PREPARING FOR LAUNCH")
+
+ sleep_until_launch()
+
+ //launch
+ radio_announce("ALERT: INITIATING LAUNCH SEQUENCE")
+ ..(user)
+
+/datum/shuttle/ferry/multidock/specops/move(var/area/origin,var/area/destination)
+ ..(origin, destination)
+ if (!location) //just arrived home
+ for(var/turf/T in get_area_turfs(destination))
+ var/mob/M = locate(/mob) in T
+ M << "\red You have arrived at Central Command. Operation has ended!"
+ else //just left for the station
+ launch_mauraders()
+ for(var/turf/T in get_area_turfs(destination))
+ var/mob/M = locate(/mob) in T
+ M << "\red You have arrived at [station_name]. Commence operation!"
+
+ reset_time = world.time + specops_return_delay //set the timeout
+
+/datum/shuttle/ferry/multidock/specops/cancel_launch()
+ if (!can_cancel())
+ return
+
+ cancel_countdown = 1
+ radio_announce("ALERT: LAUNCH SEQUENCE ABORTED")
+ if (istype(in_use, /obj/machinery/computer))
+ var/obj/machinery/computer/C = in_use
+ C.visible_message("\red Launch sequence aborted.")
+
+ ..()
+
+
+
+/datum/shuttle/ferry/multidock/specops/can_launch()
+ if(launch_prep)
+ return 0
+ return ..()
+
+//should be fine to allow forcing. process_state only becomes WAIT_LAUNCH after the countdown is over.
+///datum/shuttle/ferry/multidock/specops/can_force()
+// return 0
+
+/datum/shuttle/ferry/multidock/specops/can_cancel()
+ if(launch_prep)
+ return 1
+ return ..()
+
+/datum/shuttle/ferry/multidock/specops/proc/sleep_until_launch()
+ var/message_tracker[] = list(0,1,2,3,5,10,30,45)//Create a a list with potential time values.
+
+ var/launch_time = world.time + specops_countdown_time
+ var/time_until_launch
+
+ cancel_countdown = 0
+ launch_prep = 1
+ while(!cancel_countdown && (launch_time - world.time) > 0)
+ var/ticksleft = launch_time - world.time
+
+ //if(ticksleft > 1e5)
+ // launch_time = world.timeofday + 10 // midnight rollover
+ time_until_launch = (ticksleft / 10)
+
+ //All this does is announce the time before launch.
+ var/rounded_time_left = round(time_until_launch)//Round time so that it will report only once, not in fractions.
+ if(rounded_time_left in message_tracker)//If that time is in the list for message announce.
+ radio_announce("ALERT: [rounded_time_left] SECOND[(rounded_time_left!=1)?"S":""] REMAIN")
+ message_tracker -= rounded_time_left//Remove the number from the list so it won't be called again next cycle.
+ //Should call all the numbers but lag could mean some issues. Oh well. Not much I can do about that.
+
+ sleep(5)
+
+ launch_prep = 0
+
+
+/proc/launch_mauraders()
+ var/area/centcom/specops/special_ops = locate()//Where is the specops area located?
+ //Begin Marauder launchpad.
+ spawn(0)//So it parallel processes it.
+ for(var/obj/machinery/door/poddoor/M in special_ops)
+ switch(M.id)
+ if("ASSAULT0")
+ spawn(10)//1 second delay between each.
+ M.open()
+ if("ASSAULT1")
+ spawn(20)
+ M.open()
+ if("ASSAULT2")
+ spawn(30)
+ M.open()
+ if("ASSAULT3")
+ spawn(40)
+ M.open()
+
+ sleep(10)
+
+ var/spawn_marauder[] = new()
+ for(var/obj/effect/landmark/L in world)
+ if(L.name == "Marauder Entry")
+ spawn_marauder.Add(L)
+ for(var/obj/effect/landmark/L in world)
+ if(L.name == "Marauder Exit")
+ var/obj/effect/portal/P = new(L.loc)
+ P.invisibility = 101//So it is not seen by anyone.
+ P.failchance = 0//So it has no fail chance when teleporting.
+ P.target = pick(spawn_marauder)//Where the marauder will arrive.
+ spawn_marauder.Remove(P.target)
+
+ sleep(10)
+
+ for(var/obj/machinery/mass_driver/M in special_ops)
+ switch(M.id)
+ if("ASSAULT0")
+ spawn(10)
+ M.drive()
+ if("ASSAULT1")
+ spawn(20)
+ M.drive()
+ if("ASSAULT2")
+ spawn(30)
+ M.drive()
+ if("ASSAULT3")
+ spawn(40)
+ M.drive()
+
+ sleep(50)//Doors remain open for 5 seconds.
+
+ for(var/obj/machinery/door/poddoor/M in special_ops)
+ switch(M.id)//Doors close at the same time.
+ if("ASSAULT0")
+ spawn(0)
+ M.close()
+ if("ASSAULT1")
+ spawn(0)
+ M.close()
+ if("ASSAULT2")
+ spawn(0)
+ M.close()
+ if("ASSAULT3")
+ spawn(0)
+ M.close()
+ special_ops.readyreset()//Reset firealarm after the team launched.
+ //End Marauder launchpad.
\ No newline at end of file
diff --git a/code/modules/shuttles/shuttle_supply.dm b/code/modules/shuttles/shuttle_supply.dm
new file mode 100644
index 00000000..82f921ff
--- /dev/null
+++ b/code/modules/shuttles/shuttle_supply.dm
@@ -0,0 +1,79 @@
+
+
+
+
+
+/datum/shuttle/ferry/supply
+ var/away_location = 1 //the location to hide at while pretending to be in-transit
+ var/late_chance = 80
+ var/max_late_time = 300
+
+/datum/shuttle/ferry/supply/short_jump(var/area/origin,var/area/destination)
+ if(moving_status != SHUTTLE_IDLE)
+ return
+
+ if(isnull(location))
+ return
+
+ if(!destination)
+ destination = get_location_area(!location)
+ if(!origin)
+ origin = get_location_area(location)
+
+ //it would be cool to play a sound here
+ moving_status = SHUTTLE_WARMUP
+ spawn(warmup_time*10)
+ if (moving_status == SHUTTLE_IDLE)
+ return //someone cancelled the launch
+
+ if (at_station() && forbidden_atoms_check())
+ //cancel the launch because of forbidden atoms. announce over supply channel?
+ moving_status = SHUTTLE_IDLE
+ return
+
+ if (!at_station()) //at centcom
+ supply_controller.buy()
+
+ //We pretend it's a long_jump by making the shuttle stay at centcom for the "in-transit" period.
+ var/area/away_area = get_location_area(away_location)
+ moving_status = SHUTTLE_INTRANSIT
+
+ //If we are at the away_area then we are just pretending to move, otherwise actually do the move
+ if (origin != away_area)
+ move(origin, away_area)
+
+ //wait ETA here.
+ arrive_time = world.time + supply_controller.movetime
+ while (world.time <= arrive_time)
+ sleep(5)
+
+ if (destination != away_area)
+ //late
+ if (prob(late_chance))
+ sleep(rand(0,max_late_time))
+
+ move(away_area, destination)
+
+ moving_status = SHUTTLE_IDLE
+
+ if (!at_station()) //at centcom
+ supply_controller.sell()
+
+// returns 1 if the supply shuttle should be prevented from moving because it contains forbidden atoms
+/datum/shuttle/ferry/supply/proc/forbidden_atoms_check()
+ if (!at_station())
+ return 0 //if badmins want to send mobs or a nuke on the supply shuttle from centcom we don't care
+
+ return supply_controller.forbidden_atoms_check(get_location_area())
+
+/datum/shuttle/ferry/supply/proc/at_station()
+ return (!location)
+
+//returns 1 if the shuttle is idle and we can still mess with the cargo shopping list
+/datum/shuttle/ferry/supply/proc/idle()
+ return (moving_status == SHUTTLE_IDLE)
+
+//returns the ETA in minutes
+/datum/shuttle/ferry/supply/proc/eta_minutes()
+ var/ticksleft = arrive_time - world.time
+ return round(ticksleft/600,1)
\ No newline at end of file
diff --git a/code/modules/shuttles/shuttles_multi.dm b/code/modules/shuttles/shuttles_multi.dm
new file mode 100644
index 00000000..83c50710
--- /dev/null
+++ b/code/modules/shuttles/shuttles_multi.dm
@@ -0,0 +1,143 @@
+//This is a holder for things like the Vox and Nuke shuttle.
+/datum/shuttle/multi_shuttle
+
+ var/cloaked = 1
+ var/at_origin = 1
+ var/returned_home = 0
+ var/move_time = 240
+ var/cooldown = 20
+ var/last_move = 0 //the time at which we last moved
+
+ var/announcer
+ var/arrival_message
+ var/departure_message
+
+ var/area/interim
+ var/area/last_departed
+ var/list/destinations
+ var/area/origin
+ var/return_warning = 0
+
+/datum/shuttle/multi_shuttle/New()
+ ..()
+ if(origin) last_departed = origin
+
+/datum/shuttle/multi_shuttle/move(var/area/origin, var/area/destination)
+ ..()
+ last_move = world.time
+ if (destination == src.origin)
+ returned_home = 1
+
+/datum/shuttle/multi_shuttle/proc/announce_departure()
+
+ if(cloaked || isnull(departure_message))
+ return
+
+ command_alert(departure_message,(announcer ? announcer : "Central Command"))
+
+/datum/shuttle/multi_shuttle/proc/announce_arrival()
+
+ if(cloaked || isnull(arrival_message))
+ return
+
+ command_alert(arrival_message,(announcer ? announcer : "Central Command"))
+
+
+/obj/machinery/computer/shuttle_control/multi
+ icon_state = "syndishuttle"
+
+/obj/machinery/computer/shuttle_control/multi/attack_hand(user as mob)
+
+ if(..(user))
+ return
+ src.add_fingerprint(user)
+
+ var/datum/shuttle/multi_shuttle/MS = shuttle_controller.shuttles[shuttle_tag]
+ if(!istype(MS)) return
+
+ var/dat
+ dat = "[shuttle_tag] Ship Control "
+
+
+ if(MS.moving_status != SHUTTLE_IDLE)
+ dat += "Location: Moving "
+ else
+ var/area/areacheck = get_area(src)
+ dat += "Location: [areacheck.name] "
+
+ if((MS.last_move + MS.cooldown*10) > world.time)
+ dat += "Engines charging. "
+ else
+ dat += "Engines ready. "
+
+ dat += " Toggle cloaking field "
+ dat += "Move ship "
+ dat += "Return to base"
+
+ user << browse("[dat]", "window=[shuttle_tag]shuttlecontrol;size=300x600")
+
+/obj/machinery/computer/shuttle_control/multi/Topic(href, href_list)
+ if(..())
+ return
+
+ usr.set_machine(src)
+ src.add_fingerprint(usr)
+
+ var/datum/shuttle/multi_shuttle/MS = shuttle_controller.shuttles[shuttle_tag]
+ if(!istype(MS)) return
+
+ //world << "multi_shuttle: last_departed=[MS.last_departed], origin=[MS.origin], interim=[MS.interim], travel_time=[MS.move_time]"
+
+ if (MS.moving_status != SHUTTLE_IDLE)
+ usr << "\blue [shuttle_tag] vessel is moving."
+ return
+
+ if(href_list["start"])
+
+ if(MS.at_origin)
+ usr << "\red You are already at your home base."
+ return
+
+ if(!MS.return_warning)
+ usr << "\red Returning to your home base will end your mission. If you are sure, press the button again."
+ //TODO: Actually end the mission.
+ MS.return_warning = 1
+ return
+
+ MS.long_jump(MS.last_departed,MS.origin,MS.interim,MS.move_time)
+ MS.last_departed = MS.origin
+ MS.at_origin = 1
+
+ if(href_list["toggle_cloak"])
+
+ MS.cloaked = !MS.cloaked
+ usr << "\red Ship stealth systems have been [(MS.cloaked ? "activated. The station will not" : "deactivated. The station will")] be warned of our arrival."
+
+ if(href_list["move_multi"])
+ if((MS.last_move + MS.cooldown*10) > world.time)
+ usr << "\red The ship's drive is inoperable while the engines are charging."
+ return
+
+ var/choice = input("Select a destination.") as null|anything in MS.destinations
+ if(!choice) return
+
+ usr << "\blue [shuttle_tag] main computer recieved message."
+
+ if(MS.at_origin)
+ MS.announce_arrival()
+ MS.last_departed = MS.origin
+ MS.at_origin = 0
+
+
+ MS.long_jump(MS.last_departed, MS.destinations[choice], MS.interim, MS.move_time)
+ MS.last_departed = MS.destinations[choice]
+ return
+
+ else if(choice == MS.origin)
+
+ MS.announce_departure()
+
+ MS.short_jump(MS.last_departed, MS.destinations[choice])
+ MS.last_departed = MS.destinations[choice]
+
+ updateUsrDialog()
\ No newline at end of file
diff --git a/code/modules/supermatter/supermatter.dm b/code/modules/supermatter/supermatter.dm
index ed13d9ad..69dd0019 100644
--- a/code/modules/supermatter/supermatter.dm
+++ b/code/modules/supermatter/supermatter.dm
@@ -1,13 +1,27 @@
-#define NITROGEN_RETARDATION_FACTOR 4 //Higher == N2 slows reaction more
-#define THERMAL_RELEASE_MODIFIER 10 //Higher == less heat released during reaction
-#define PLASMA_RELEASE_MODIFIER 1500 //Higher == less plasma released by reaction
-#define OXYGEN_RELEASE_MODIFIER 750 //Higher == less oxygen released at high temperature/power
+#define NITROGEN_RETARDATION_FACTOR 0.15 //Higher == N2 slows reaction more
#define THERMAL_RELEASE_MODIFIER 750 //Higher == more heat released during reaction
#define PLASMA_RELEASE_MODIFIER 1500 //Higher == less plasma released by reaction
#define OXYGEN_RELEASE_MODIFIER 1500 //Higher == less oxygen released at high temperature/power
#define REACTION_POWER_MODIFIER 1.1 //Higher == more overall power
+/*
+ How to tweak the SM
+
+ POWER_FACTOR directly controls how much power the SM puts out at a given level of excitation (power var). Making this lower means you have to work the SM harder to get the same amount of power.
+ CRITICAL_TEMPERATURE The temperature at which the SM starts taking damage.
+
+ CHARGING_FACTOR Controls how much emitter shots excite the SM.
+ DAMAGE_RATE_LIMIT Controls the maximum rate at which the SM will take damage due to high temperatures.
+*/
+
+//Controls how much power is produced by each collector in range - this is the main parameter for tweaking SM balance, as it basically controls how the power variable relates to the rest of the game.
+#define POWER_FACTOR 1.0
+#define DECAY_FACTOR 700 //Affects how fast the supermatter power decays
+#define CRITICAL_TEMPERATURE 800 //K
+#define CHARGING_FACTOR 0.05
+#define DAMAGE_RATE_LIMIT 3 //damage rate cap at power = 300, scales linearly with power
+
//These would be what you would get at point blank, decreases with distance
#define DETONATION_RADS 200
@@ -38,6 +52,13 @@
var/emergency_alert = "CRYSTAL DELAMINATION IMMINENT."
var/explosion_point = 1000
+ l_color = "#8A8A00"
+ var/warning_color = "#B8B800"
+ var/emergency_color = "#D9D900"
+
+ var/grav_pulling = 0
+ var/pull_radius = 14
+
var/emergency_issued = 0
var/explosion_power = 8
@@ -57,6 +78,8 @@
var/obj/item/device/radio/radio
+ var/debug = 0
+
shard //Small subtype, less efficient and more sensitive, but less boom.
name = "Supermatter Shard"
desc = "A strangely translucent and iridescent crystal that looks like it used to be part of a larger structure. \red You get headaches just from looking at it."
@@ -69,6 +92,7 @@
gasefficency = 0.125
+ pull_radius = 5
explosion_power = 3 //3,6,9,12? Or is that too small?
@@ -82,10 +106,20 @@
. = ..()
/obj/machinery/power/supermatter/proc/explode()
+ anchored = 1
+ grav_pulling = 1
+ spawn(100)
explosion(get_turf(src), explosion_power, explosion_power * 2, explosion_power * 3, explosion_power * 4, 1)
del src
return
+//Changes color and luminosity of the light to these values if they were not already set
+/obj/machinery/power/supermatter/proc/shift_light(var/lum, var/clr)
+ if(l_color != clr)
+ l_color = clr
+ if(luminosity != lum)
+ SetLuminosity(lum)
+
/obj/machinery/power/supermatter/process()
var/turf/L = loc
@@ -96,106 +130,114 @@
if(!istype(L)) //We are in a crate or somewhere that isn't turf, if we return to turf resume processing but for now.
return //Yeah just stop.
- if(istype(L, /turf/space)) // Stop processing this stuff if we've been ejected.
- return
if(damage > warning_point) // while the core is still damaged and it's still worth noting its status
+
+ shift_light(5, warning_color)
if((world.timeofday - lastwarning) / 10 >= WARNING_DELAY)
var/stability = num2text(round((damage / explosion_point) * 100))
+ var/alert_msg
if(damage > emergency_point)
-
- radio.autosay(addtext(emergency_alert, " Instability: ",stability,"%"), "Supermatter Monitor")
+ shift_light(7, emergency_color)
+ alert_msg = addtext(emergency_alert, " Instability: ",stability,"%")
lastwarning = world.timeofday
-
else if(damage >= damage_archived) // The damage is still going up
- radio.autosay(addtext(warning_alert," Instability: ",stability,"%"), "Supermatter Monitor")
+ alert_msg = addtext(warning_alert," Instability: ",stability,"%")
lastwarning = world.timeofday - 150
-
- else // Phew, we're safe
- radio.autosay(safe_alert, "Supermatter Monitor")
+ else // Phew, we're safe
+ alert_msg = safe_alert
lastwarning = world.timeofday
+ if(!istype(L, /turf/space) && alert_msg)
+ radio.autosay(alert_msg, "Supermatter Monitor")
+
if(damage > explosion_point)
for(var/mob/living/mob in living_mob_list)
- if(istype(mob, /mob/living/carbon/human))
- //Hilariously enough, running into a closet should make you get hit the hardest.
- mob:hallucination += max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(mob, src) + 1)) ) )
- var/rads = DETONATION_RADS * sqrt( 1 / (get_dist(mob, src) + 1) )
- mob.apply_effect(rads, IRRADIATE)
+ if(loc.z == mob.loc.z)
+ if(istype(mob, /mob/living/carbon/human))
+ //Hilariously enough, running into a closet should make you get hit the hardest.
+ var/mob/living/carbon/human/H = mob
+ H.hallucination += max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(mob, src) + 1)) ) )
+ var/rads = DETONATION_RADS * sqrt( 1 / (get_dist(mob, src) + 1) )
+ mob.apply_effect(rads, IRRADIATE)
explode()
+ else
+ shift_light(4,initial(l_color))
+ if(grav_pulling)
+ supermatter_pull()
//Ok, get the air from the turf
- var/datum/gas_mixture/env = L.return_air()
+ var/datum/gas_mixture/removed = null
+ var/datum/gas_mixture/env = null
- //Remove gas from surrounding area
- var/datum/gas_mixture/removed = env.remove(gasefficency * env.total_moles)
+ //ensure that damage doesn't increase too quickly due to super high temperatures resulting from no coolant, for example. We dont want the SM exploding before anyone can react.
+ //We want the cap to scale linearly with power (and explosion_point). Let's aim for a cap of 5 at power = 300 (based on testing, equals roughly 5% per SM alert announcement).
+ var/damage_inc_limit = (power/300)*(explosion_point/1000)*DAMAGE_RATE_LIMIT
- if(!removed || !removed.total_moles)
- damage += max((power-1600)/10, 0)
- power = min(power, 1600)
- return 1
+ if(!istype(L, /turf/space))
+ env = L.return_air()
+ removed = env.remove(gasefficency * env.total_moles) //Remove gas from surrounding area
- if (!removed)
- return 1
-
- damage_archived = damage
- damage = max( damage + ( (removed.temperature - 800) / 150 ) , 0 )
- //Ok, 100% oxygen atmosphere = best reaction
- //Maxes out at 100% oxygen pressure
- oxygen = max(min((removed.oxygen - (removed.nitrogen * NITROGEN_RETARDATION_FACTOR)) / MOLES_CELLSTANDARD, 1), 0)
-
- var/temp_factor = 100
-
- if(oxygen > 0.8)
- // with a perfect gas mix, make the power less based on heat
- icon_state = "[base_icon_state]_glow"
+ if(!env || !removed || !removed.total_moles)
+ damage += max((power - 15*POWER_FACTOR)/10, 0)
+ else if (grav_pulling) //If supermatter is detonating, remove all air from the zone
+ env.remove(env.total_moles)
else
- // in normal mode, base the produced energy around the heat
- temp_factor = 60
- icon_state = base_icon_state
+ damage_archived = damage
- power = max( (removed.temperature * temp_factor / T0C) * oxygen + power, 0) //Total laser power plus an overload
+ damage = max( damage + min( ( (removed.temperature - CRITICAL_TEMPERATURE) / 150 ), damage_inc_limit ) , 0 )
+ //Ok, 100% oxygen atmosphere = best reaction
+ //Maxes out at 100% oxygen pressure
+ oxygen = max(min((removed.gas["oxygen"] - (removed.gas["nitrogen"] * NITROGEN_RETARDATION_FACTOR)) / removed.total_moles, 1), 0)
- //We've generated power, now let's transfer it to the collectors for storing/usage
- transfer_energy()
+ //calculate power gain for oxygen reaction
+ var/temp_factor
+ var/equilibrium_power
+ if (oxygen > 0.8)
+ //If chain reacting at oxygen == 1, we want the power at 800 K to stabilize at a power level of 400
+ equilibrium_power = 400
+ icon_state = "[base_icon_state]_glow"
+ else
+ //If chain reacting at oxygen == 1, we want the power at 800 K to stabilize at a power level of 250
+ equilibrium_power = 250
+ icon_state = base_icon_state
- var/device_energy = power * REACTION_POWER_MODIFIER
+ temp_factor = ( (equilibrium_power/DECAY_FACTOR)**3 )/800
+ power = max( (removed.temperature * temp_factor) * oxygen + power, 0)
- //To figure out how much temperature to add each tick, consider that at one atmosphere's worth
- //of pure oxygen, with all four lasers firing at standard energy and no N2 present, at room temperature
- //that the device energy is around 2140. At that stage, we don't want too much heat to be put out
- //Since the core is effectively "cold"
+ //We've generated power, now let's transfer it to the collectors for storing/usage
+ transfer_energy()
- //Also keep in mind we are only adding this temperature to (efficiency)% of the one tile the rock
- //is on. An increase of 4*C @ 25% efficiency here results in an increase of 1*C / (#tilesincore) overall.
-
- var/thermal_power = THERMAL_RELEASE_MODIFIER
- if(removed.total_moles < 35) thermal_power += 750 //If you don't add coolant, you are going to have a bad time.
+ var/device_energy = power * REACTION_POWER_MODIFIER
- removed.temperature += ((device_energy * thermal_power) / max(1, removed.heat_capacity()))
+ //Release reaction gasses
+ var/heat_capacity = removed.heat_capacity()
+ removed.adjust_multi("toxins", max(device_energy / PLASMA_RELEASE_MODIFIER, 0), \
+ "oxygen", max((device_energy + removed.temperature - T0C) / OXYGEN_RELEASE_MODIFIER, 0))
- removed.temperature = max(0, min(removed.temperature, 10000))
+ var/thermal_power = THERMAL_RELEASE_MODIFIER * device_energy
+ if (debug)
+ var/heat_capacity_new = removed.heat_capacity()
+ visible_message("[src]: Releasing [round(thermal_power)] W.")
+ visible_message("[src]: Releasing additional [round((heat_capacity_new - heat_capacity)*removed.temperature)] W with exhaust gasses.")
- //Calculate how much gas to release
- removed.toxins += max(device_energy / PLASMA_RELEASE_MODIFIER, 0)
+ removed.add_thermal_energy(thermal_power)
+ removed.temperature = between(0, removed.temperature, 10000)
- removed.oxygen += max((device_energy + removed.temperature - T0C) / OXYGEN_RELEASE_MODIFIER, 0)
+ env.merge(removed)
- removed.update_values()
-
- env.merge(removed)
-
- for(var/mob/living/carbon/human/l in view(src, min(7, round(power ** 0.25)))) // If they can see it without mesons on. Bad on them.
+ for(var/mob/living/carbon/human/l in view(src, min(7, round(sqrt(power/6))))) // If they can see it without mesons on. Bad on them.
if(!istype(l.glasses, /obj/item/clothing/glasses/meson))
l.hallucination = max(0, min(200, l.hallucination + power * config_hallucination_power * sqrt( 1 / max(1,get_dist(l, src)) ) ) )
- for(var/mob/living/l in range(src, 8))
+ //adjusted range so that a power of 300 (pretty high) results in 8 tiles, roughly the distance from the core to the engine monitoring room.
+ for(var/mob/living/l in range(src, round(sqrt(power / 5))))
var/rads = (power / 10) * sqrt( 1 / get_dist(l, src) )
l.apply_effect(rads, IRRADIATE)
- power -= (power/500)**3
+ power -= (power/DECAY_FACTOR)**3 //energy losses due to radiation
return 1
@@ -207,8 +249,8 @@
// Then bring it inside to explode instantly upon landing on a valid turf.
- if(Proj.flag != "bullet")
- power += Proj.damage * config_bullet_energy
+ if(istype(Proj, /obj/item/projectile/beam))
+ power += Proj.damage * config_bullet_energy * CHARGING_FACTOR / POWER_FACTOR
else
damage += Proj.damage * config_bullet_energy
return 0
@@ -237,8 +279,10 @@
/obj/machinery/power/supermatter/proc/transfer_energy()
for(var/obj/machinery/power/rad_collector/R in rad_collectors)
- if(get_dist(R, src) <= 15) // Better than using orange() every process
- R.receive_pulse(power)
+ var/distance = get_dist(R, src)
+ if(distance <= 15)
+ //for collectors using standard phoron tanks at 1013 kPa, the actual power generated will be this power*POWER_FACTOR*20*29 = power*POWER_FACTOR*580
+ R.receive_pulse(power * POWER_FACTOR * (min(3/distance, 1))**2)
return
/obj/machinery/power/supermatter/attackby(obj/item/weapon/W as obj, mob/living/user as mob)
@@ -257,7 +301,7 @@
AM.visible_message("\The [AM] slams into \the [src] inducing a resonance... \his body starts to glow and catch flame before flashing into ash.",\
"You slam into \the [src] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\"",\
"You hear an uneartly ringing, then what sounds like a shrilling kettle as you are washed with a wave of heat.")
- else
+ else if(!grav_pulling) //To prevent spam, detonating supermatter does not indicate non-mobs being destroyed
AM.visible_message("\The [AM] smacks into \the [src] and rapidly flashes to ash.",\
"You hear a loud crack as you are washed with a wave of heat.")
@@ -283,3 +327,45 @@
var/rads = 500 * sqrt( 1 / (get_dist(l, src) + 1) )
l.apply_effect(rads, IRRADIATE)
+
+/obj/machinery/power/supermatter/proc/supermatter_pull()
+
+ //following is adapted from singulo code
+ if(defer_powernet_rebuild != 2)
+ defer_powernet_rebuild = 1
+ // Let's just make this one loop.
+ for(var/atom/X in orange(pull_radius,src))
+ // Movable atoms only
+ if(istype(X, /atom/movable))
+ if(is_type_in_list(X, uneatable)) continue
+ if(((X) && (!istype(X,/mob/living/carbon/human))))
+ step_towards(X,src)
+ if(istype(X, /obj)) //unanchored objects pulled twice as fast
+ var/obj/O = X
+ if(!O.anchored)
+ step_towards(X,src)
+ else
+ step_towards(X,src)
+ if(istype(X, /obj/structure/window)) //shatter windows
+ var/obj/structure/window/W = X
+ W.ex_act(2.0)
+ else if(istype(X,/mob/living/carbon/human))
+ var/mob/living/carbon/human/H = X
+ if(istype(H.shoes,/obj/item/clothing/shoes/magboots))
+ var/obj/item/clothing/shoes/magboots/M = H.shoes
+ if(M.magpulse)
+ step_towards(H,src) //step just once with magboots
+ continue
+ step_towards(H,src) //step twice
+ step_towards(H,src)
+
+ if(defer_powernet_rebuild != 2)
+ defer_powernet_rebuild = 0
+ return
+
+
+/obj/machinery/power/supermatter/GotoAirflowDest(n) //Supermatter not pushed around by airflow
+ return
+
+/obj/machinery/power/supermatter/RepelAirflowDest(n)
+ return
\ No newline at end of file
diff --git a/code/modules/surgery/braincore.dm b/code/modules/surgery/braincore.dm
index b89c4827..0d4940f3 100644
--- a/code/modules/surgery/braincore.dm
+++ b/code/modules/surgery/braincore.dm
@@ -107,6 +107,7 @@
B.transfer_identity(target)
target.internal_organs -= B
+ target.internal_organs_by_name -= "brain"
target:brain_op_stage = 4.0
target.death()//You want them to die after the brain was transferred, so not to trigger client death() twice.
@@ -173,7 +174,7 @@
end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("\blue [user] mends hematoma in [target]'s brain with \the [tool].", \
"\blue You mend hematoma in [target]'s brain with \the [tool].")
- var/datum/organ/internal/brain/sponge = target.internal_organs["brain"]
+ var/datum/organ/internal/brain/sponge = target.internal_organs_by_name["brain"]
if (sponge)
sponge.damage = 0
@@ -187,9 +188,12 @@
// SLIME CORE EXTRACTION //
//////////////////////////////////////////////////////////////////
-/datum/surgery_step/slime/
+/datum/surgery_step/slime
+ is_valid_target(mob/living/carbon/slime/target)
+ return istype(target, /mob/living/carbon/slime/)
+
can_use(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
- return istype(target, /mob/living/carbon/slime/) && target.stat == 2
+ return target.stat == 2
/datum/surgery_step/slime/cut_flesh
allowed_tools = list(
@@ -210,7 +214,7 @@
end_step(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
user.visible_message("\blue [user] cuts through [target]'s flesh with \the [tool].", \
- "\blue You cut through [target]'s flesh with \the [tool], exposing the cores.")
+ "\blue You cut through [target]'s flesh with \the [tool], revealing its silky innards.")
target.brain_op_stage = 1
fail_step(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
@@ -267,8 +271,7 @@
if(target.cores >= 0)
new target.coretype(target.loc)
if(target.cores <= 0)
- var/origstate = initial(target.icon_state)
- target.icon_state = "[origstate] dead-nocore"
+ target.icon_state = "[target.colour] baby slime dead-nocore"
fail_step(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
diff --git a/code/modules/surgery/eye.dm b/code/modules/surgery/eye.dm
index 9977271c..69e23867 100644
--- a/code/modules/surgery/eye.dm
+++ b/code/modules/surgery/eye.dm
@@ -39,7 +39,7 @@
target.blinded += 1.5
fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/internal/eyes/eyes = target.internal_organs["eyes"]
+ var/datum/organ/internal/eyes/eyes = target.internal_organs_by_name["eyes"]
var/datum/organ/external/affected = target.get_organ(target_zone)
user.visible_message("\red [user]'s hand slips, slicing [target]'s eyes wth \the [tool]!" , \
"\red Your hand slips, slicing [target]'s eyes wth \the [tool]!" )
@@ -69,7 +69,7 @@
target.op_stage.eyes = 2
fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/internal/eyes/eyes = target.internal_organs["eyes"]
+ var/datum/organ/internal/eyes/eyes = target.internal_organs_by_name["eyes"]
var/datum/organ/external/affected = target.get_organ(target_zone)
user.visible_message("\red [user]'s hand slips, damaging [target]'s eyes with \the [tool]!", \
"\red Your hand slips, damaging [target]'s eyes with \the [tool]!")
@@ -100,7 +100,7 @@
target.op_stage.eyes = 3
fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/internal/eyes/eyes = target.internal_organs["eyes"]
+ var/datum/organ/internal/eyes/eyes = target.internal_organs_by_name["eyes"]
var/datum/organ/external/affected = target.get_organ(target_zone)
user.visible_message("\red [user]'s hand slips, stabbing \the [tool] into [target]'s eye!", \
"\red Your hand slips, stabbing \the [tool] into [target]'s eye!")
@@ -126,7 +126,7 @@
"You are beginning to cauterize the incision around [target]'s eyes with \the [tool].")
end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/internal/eyes/eyes = target.internal_organs["eyes"]
+ var/datum/organ/internal/eyes/eyes = target.internal_organs_by_name["eyes"]
user.visible_message("\blue [user] cauterizes the incision around [target]'s eyes with \the [tool].", \
"\blue You cauterize the incision around [target]'s eyes with \the [tool].")
if (target.op_stage.eyes == 3)
@@ -136,7 +136,7 @@
target.op_stage.eyes = 0
fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/internal/eyes/eyes = target.internal_organs["eyes"]
+ var/datum/organ/internal/eyes/eyes = target.internal_organs_by_name["eyes"]
var/datum/organ/external/affected = target.get_organ(target_zone)
user.visible_message("\red [user]'s hand slips, searing [target]'s eyes with \the [tool]!", \
"\red Your hand slips, searing [target]'s eyes with \the [tool]!")
diff --git a/code/modules/surgery/headreattach.dm b/code/modules/surgery/headreattach.dm
index 17db2c30..72da44cc 100644
--- a/code/modules/surgery/headreattach.dm
+++ b/code/modules/surgery/headreattach.dm
@@ -64,7 +64,7 @@
can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(..())
var/datum/organ/external/affected = target.get_organ(target_zone)
- return affected.status & ORGAN_CUT_AWAY && affected.open < 3 && !(affected.status & ORGAN_ATTACHABLE)
+ return affected.status & ORGAN_CUT_AWAY && target.op_stage.head_reattach == 0 && !(affected.status & ORGAN_ATTACHABLE)
begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/datum/organ/external/affected = target.get_organ(target_zone)
@@ -73,10 +73,9 @@
..()
end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/external/affected = target.get_organ(target_zone)
user.visible_message("\blue [user] has finished repositioning flesh and tissue to something anatomically recognizable where [target]'s head used to be with \the [tool].", \
"\blue You have finished repositioning flesh and tissue to something anatomically recognizable where [target]'s head used to be with \the [tool].")
- affected.open = 3
+ target.op_stage.head_reattach = 1
fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/datum/organ/external/affected = target.get_organ(target_zone)
@@ -97,8 +96,7 @@
can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(..())
- var/datum/organ/external/affected = target.get_organ(target_zone)
- return affected.open == 3
+ return target.op_stage.head_reattach == 1
begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("[user] is stapling and suturing flesh into place in [target]'s esophagal and vocal region with \the [tool].", \
@@ -106,10 +104,9 @@
..()
end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/external/affected = target.get_organ(target_zone)
user.visible_message("\blue [user] has finished stapling [target]'s neck into place with \the [tool].", \
"\blue You have finished stapling [target]'s neck into place with \the [tool].")
- affected.open = 4
+ target.op_stage.head_reattach = 2
fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/datum/organ/external/affected = target.get_organ(target_zone)
@@ -132,8 +129,7 @@
can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(..())
- var/datum/organ/external/affected = target.get_organ(target_zone)
- return affected.open == 4
+ return target.op_stage.head_reattach == 2
begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("[user] starts adjusting area around [target]'s neck with \the [tool].", \
@@ -144,6 +140,7 @@
var/datum/organ/external/affected = target.get_organ(target_zone)
user.visible_message("\blue [user] has finished adjusting the area around [target]'s neck with \the [tool].", \
"\blue You have finished adjusting the area around [target]'s neck with \the [tool].")
+ target.op_stage.head_reattach = 0
affected.status |= ORGAN_ATTACHABLE
affected.amputated = 1
affected.setAmputatedTree()
diff --git a/code/modules/surgery/implant.dm b/code/modules/surgery/implant.dm
index c3f4f329..b2352dcf 100644
--- a/code/modules/surgery/implant.dm
+++ b/code/modules/surgery/implant.dm
@@ -128,7 +128,7 @@
"\blue You put \the [tool] inside [target]'s [get_cavity(affected)] cavity." )
if (tool.w_class > get_max_wclass(affected)/2 && prob(50))
user << "\red You tear some blood vessels trying to fit such a big object in this cavity."
- var/datum/wound/internal_bleeding/I = new (15)
+ var/datum/wound/internal_bleeding/I = new (10)
affected.wounds += I
affected.owner.custom_pain("You feel something rip in your [affected.display_name]!", 1)
user.drop_item()
diff --git a/code/modules/surgery/other.dm b/code/modules/surgery/other.dm
index bd1002b0..7d113950 100644
--- a/code/modules/surgery/other.dm
+++ b/code/modules/surgery/other.dm
@@ -51,3 +51,121 @@
user.visible_message("\red [user]'s hand slips, smearing [tool] in the incision in [target]'s [affected.display_name]!" , \
"\red Your hand slips, smearing [tool] in the incision in [target]'s [affected.display_name]!")
affected.take_damage(5, 0)
+
+/datum/surgery_step/fix_dead_tissue //Debridement
+ priority = 2
+ allowed_tools = list(
+ /obj/item/weapon/scalpel = 100, \
+ /obj/item/weapon/kitchenknife = 75, \
+ /obj/item/weapon/shard = 50, \
+ )
+
+ can_infect = 1
+ blood_level = 1
+
+ min_duration = 110
+ max_duration = 160
+
+ can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if(!hasorgans(target))
+ return 0
+
+ if (target_zone == "mouth" || target_zone == "eyes")
+ return 0
+
+ var/datum/organ/external/affected = target.get_organ(target_zone)
+
+ return affected.open == 2 && (affected.status & ORGAN_DEAD)
+
+ begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ var/datum/organ/external/affected = target.get_organ(target_zone)
+ user.visible_message("[user] starts cutting away necrotic tissue in [target]'s [affected.display_name] with \the [tool]." , \
+ "You start cutting away necrotic tissue in [target]'s [affected.display_name] with \the [tool].")
+ target.custom_pain("The pain in [affected.display_name] is unbearable!",1)
+ ..()
+
+ end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ var/datum/organ/external/affected = target.get_organ(target_zone)
+ user.visible_message("\blue [user] has cut away necrotic tissue in [target]'s [affected.display_name] with \the [tool].", \
+ "\blue You have cut away necrotic tissue in [target]'s [affected.display_name] with \the [tool].")
+ affected.open = 3
+
+ fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ var/datum/organ/external/affected = target.get_organ(target_zone)
+ user.visible_message("\red [user]'s hand slips, slicing an artery inside [target]'s [affected.display_name] with \the [tool]!", \
+ "\red Your hand slips, slicing an artery inside [target]'s [affected.display_name] with \the [tool]!")
+ affected.createwound(CUT, 20, 1)
+
+/datum/surgery_step/treat_necrosis
+ priority = 2
+ allowed_tools = list(
+ /obj/item/weapon/reagent_containers/dropper = 100,
+ /obj/item/weapon/reagent_containers/glass/bottle = 75,
+ /obj/item/weapon/reagent_containers/glass/beaker = 75,
+ /obj/item/weapon/reagent_containers/spray = 50,
+ /obj/item/weapon/reagent_containers/glass/bucket = 50,
+ )
+
+ can_infect = 0
+ blood_level = 0
+
+ min_duration = 50
+ max_duration = 60
+
+ can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ if (!istype(tool, /obj/item/weapon/reagent_containers))
+ return 0
+
+ var/obj/item/weapon/reagent_containers/container = tool
+ if(!container.reagents.has_reagent("peridaxon"))
+ return 0
+
+ if(!hasorgans(target))
+ return 0
+
+ if (target_zone == "mouth" || target_zone == "eyes")
+ return 0
+
+ var/datum/organ/external/affected = target.get_organ(target_zone)
+ return affected.open == 3 && (affected.status & ORGAN_DEAD)
+
+ begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ var/datum/organ/external/affected = target.get_organ(target_zone)
+ user.visible_message("[user] starts applying medication to the affected tissue in [target]'s [affected.display_name] with \the [tool]." , \
+ "You start applying medication to the affected tissue in [target]'s [affected.display_name] with \the [tool].")
+ target.custom_pain("Something in your [affected.display_name] is causing you a lot of pain!",1)
+ ..()
+
+ end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ var/datum/organ/external/affected = target.get_organ(target_zone)
+
+ if (!istype(tool, /obj/item/weapon/reagent_containers))
+ return
+
+ var/obj/item/weapon/reagent_containers/container = tool
+
+ var/trans = container.reagents.trans_to(target, container.amount_per_transfer_from_this)
+ if (trans > 0)
+ container.reagents.reaction(target, INGEST) //technically it's contact, but the reagents are being applied to internal tissue
+
+ if(container.reagents.has_reagent("peridaxon"))
+ affected.status &= ~ORGAN_DEAD
+
+ user.visible_message("\blue [user] applies [trans] units of the solution to affected tissue in [target]'s [affected.display_name]", \
+ "\blue You apply [trans] units of the solution to affected tissue in [target]'s [affected.display_name] with \the [tool].")
+
+ fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ var/datum/organ/external/affected = target.get_organ(target_zone)
+
+ if (!istype(tool, /obj/item/weapon/reagent_containers))
+ return
+
+ var/obj/item/weapon/reagent_containers/container = tool
+
+ var/trans = container.reagents.trans_to(target, container.amount_per_transfer_from_this)
+ container.reagents.reaction(target, INGEST) //technically it's contact, but the reagents are being applied to internal tissue
+
+ user.visible_message("\red [user]'s hand slips, applying [trans] units of the solution to the wrong place in [target]'s [affected.display_name] with the [tool]!" , \
+ "\red Your hand slips, applying [trans] units of the solution to the wrong place in [target]'s [affected.display_name] with the [tool]!")
+
+ //no damage or anything, just wastes medicine
diff --git a/code/modules/surgery/ribcage.dm b/code/modules/surgery/ribcage.dm
index 7bbad9b5..c14d065c 100644
--- a/code/modules/surgery/ribcage.dm
+++ b/code/modules/surgery/ribcage.dm
@@ -202,9 +202,10 @@
var/is_chest_organ_damaged = 0
var/datum/organ/external/chest/chest = target.get_organ("chest")
- for(var/datum/organ/internal/I in chest.internal_organs) if(I.damage > 0)
- is_chest_organ_damaged = 1
- break
+ for(var/datum/organ/internal/I in chest.internal_organs)
+ if(I.damage > 0)
+ is_chest_organ_damaged = 1
+ break
return ..() && is_chest_organ_damaged && target.op_stage.ribcage == 2
begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -223,7 +224,7 @@
user.visible_message("[user] starts treating damage to [target]'s [I.name] with [tool_name].", \
"You start treating damage to [target]'s [I.name] with [tool_name]." )
else
- user.visible_message("\blue [user] attempts to repair [target]'s mechanical [I.name] with [tool_name]...", \
+ user.visible_message("[user] attempts to repair [target]'s mechanical [I.name] with [tool_name]...", \
"\blue You attempt to repair [target]'s mechanical [I.name] with [tool_name]...")
target.custom_pain("The pain in your chest is living hell!",1)
@@ -242,8 +243,8 @@
for(var/datum/organ/internal/I in chest.internal_organs)
if(I && I.damage > 0)
if(I.robotic < 2)
- user.visible_message("[user] treats damage to [target]'s [I.name] with [tool_name].", \
- "You treat damage to [target]'s [I.name] with [tool_name]." )
+ user.visible_message("\blue [user] treats damage to [target]'s [I.name] with [tool_name].", \
+ "\blue You treat damage to [target]'s [I.name] with [tool_name]." )
else
user.visible_message("\blue [user] pokes [target]'s mechanical [I.name] with [tool_name]...", \
"\blue You poke [target]'s mechanical [I.name] with [tool_name]... \red For no effect, since it's robotic.")
@@ -285,7 +286,7 @@
return 0
var/is_chest_organ_damaged = 0
- var/datum/organ/internal/heart/heart = target.internal_organs["heart"]
+ var/datum/organ/internal/heart/heart = target.internal_organs_by_name["heart"]
var/datum/organ/external/chest/chest = target.get_organ("chest")
for(var/datum/organ/internal/I in chest.internal_organs) if(I.damage > 0)
is_chest_organ_damaged = 1
@@ -293,7 +294,7 @@
return ..() && is_chest_organ_damaged && heart.robotic == 2 && target.op_stage.ribcage == 2
begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/internal/heart/heart = target.internal_organs["heart"]
+ var/datum/organ/internal/heart/heart = target.internal_organs_by_name["heart"]
if(heart.damage > 0)
user.visible_message("[user] starts mending the mechanisms on [target]'s heart with \the [tool].", \
@@ -302,14 +303,14 @@
..()
end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/internal/heart/heart = target.internal_organs["heart"]
+ var/datum/organ/internal/heart/heart = target.internal_organs_by_name["heart"]
if(heart.damage > 0)
user.visible_message("\blue [user] repairs [target]'s heart with \the [tool].", \
"\blue You repair [target]'s heart with \the [tool]." )
heart.damage = 0
fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/datum/organ/internal/heart/heart = target.internal_organs["heart"]
+ var/datum/organ/internal/heart/heart = target.internal_organs_by_name["heart"]
user.visible_message("\red [user]'s hand slips, smearing [tool] in the incision in [target]'s heart, gumming it up!!" , \
"\red Your hand slips, smearing [tool] in the incision in [target]'s heart, gumming it up!")
heart.take_damage(5, 0)
diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm
index 05371f18..b0116585 100644
--- a/code/modules/surgery/surgery.dm
+++ b/code/modules/surgery/surgery.dm
@@ -25,8 +25,10 @@
return allowed_tools[T]
return 0
- // Checks if this step applies to the mutantrace of the user.
- proc/is_valid_mutantrace(mob/living/carbon/human/target)
+ // Checks if this step applies to the user mob at all
+ proc/is_valid_target(mob/living/carbon/human/target)
+ if(!hasorgans(target))
+ return 0
if(allowed_species)
for(var/species in allowed_species)
@@ -40,6 +42,7 @@
return 1
+
// checks whether this step can be applied with the given user and target
proc/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return 0
@@ -81,12 +84,12 @@ proc/do_surgery(mob/living/M, mob/living/user, obj/item/tool)
return 0
for(var/datum/surgery_step/S in surgery_steps)
//check if tool is right or close enough and if this step is possible
- if( S.tool_quality(tool) && S.can_use(user, M, user.zone_sel.selecting, tool) && S.is_valid_mutantrace(M))
+ if( S.tool_quality(tool) && S.can_use(user, M, user.zone_sel.selecting, tool) && S.is_valid_target(M))
S.begin_step(user, M, user.zone_sel.selecting, tool) //start on it
//We had proper tools! (or RNG smiled.) and User did not move or change hands.
if( prob(S.tool_quality(tool)) && do_mob(user, M, rand(S.min_duration, S.max_duration)))
S.end_step(user, M, user.zone_sel.selecting, tool) //finish successfully
- else //or
+ else if (tool in user.contents && user.Adjacent(M)) //or
S.fail_step(user, M, user.zone_sel.selecting, tool) //malpractice~
return 1 //don't want to do weapony things after surgery
return 0
@@ -111,4 +114,5 @@ proc/sort_surgeries()
var/eyes = 0
var/face = 0
var/appendix = 0
- var/ribcage = 0
\ No newline at end of file
+ var/ribcage = 0
+ var/head_reattach = 0
diff --git a/code/setup.dm b/code/setup.dm
index ee89e314..e2bb9db0 100644
--- a/code/setup.dm
+++ b/code/setup.dm
@@ -6,6 +6,13 @@
#define R_IDEAL_GAS_EQUATION 8.31 //kPa*L/(K*mol)
#define ONE_ATMOSPHERE 101.325 //kPa
+#define IDEAL_GAS_ENTROPY_CONSTANT 1164 //(mol^3 * s^3) / (kg^3 * L). Equal to (4*pi/(avrogadro's number * planck's constant)^2)^(3/2) / (avrogadro's number * 1000 Liters per m^3).
+
+//radiation constants
+#define STEFAN_BOLTZMANN_CONSTANT 0.0000000567 //W/(m^2*K^4)
+#define COSMIC_RADIATION_TEMPERATURE 3.15 //K
+#define AVERAGE_SOLAR_RADIATION 200 //W/m^2. Kind of arbitrary. Really this should depend on the sun position much like solars.
+#define RADIATOR_OPTIMUM_PRESSURE 110 //kPa at 20 C
#define CELL_VOLUME 2500 //liters in a cell
#define MOLES_CELLSTANDARD (ONE_ATMOSPHERE*CELL_VOLUME/(T20C*R_IDEAL_GAS_EQUATION)) //moles in a 2.5 m^3 cell at 101.325 Pa and 20 degC
@@ -27,6 +34,8 @@
#define HUMAN_NEEDED_OXYGEN MOLES_CELLSTANDARD*BREATH_PERCENTAGE*0.16
//Amount of air needed before pass out/suffocation commences
+#define SOUND_MINIMUM_PRESSURE 10
+
// Pressure limits.
#define HAZARD_HIGH_PRESSURE 550 //This determins at what pressure the ultra-high pressure red icon is displayed. (This one is set as a constant)
#define WARNING_HIGH_PRESSURE 325 //This determins when the orange pressure icon is displayed (it is 0.7 * HAZARD_HIGH_PRESSURE)
@@ -35,10 +44,10 @@
#define TEMPERATURE_DAMAGE_COEFFICIENT 1.5 //This is used in handle_temperature_damage() for humans, and in reagents that affect body temperature. Temperature damage is multiplied by this amount.
#define BODYTEMP_AUTORECOVERY_DIVISOR 12 //This is the divisor which handles how much of the temperature difference between the current body temperature and 310.15K (optimal temperature) humans auto-regenerate each tick. The higher the number, the slower the recovery. This is applied each tick, so long as the mob is alive.
-#define BODYTEMP_AUTORECOVERY_MINIMUM 10 //Minimum amount of kelvin moved toward 310.15K per tick. So long as abs(310.15 - bodytemp) is more than 50.
+#define BODYTEMP_AUTORECOVERY_MINIMUM 1 //Minimum amount of kelvin moved toward 310.15K per tick. So long as abs(310.15 - bodytemp) is more than 50.
#define BODYTEMP_COLD_DIVISOR 6 //Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is lower than their body temperature. Make it lower to lose bodytemp faster.
#define BODYTEMP_HEAT_DIVISOR 6 //Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is higher than their body temperature. Make it lower to gain bodytemp faster.
-#define BODYTEMP_COOLING_MAX 30 //The maximum number of degrees that your body can cool in 1 tick, when in a cold area.
+#define BODYTEMP_COOLING_MAX -30 //The maximum number of degrees that your body can cool in 1 tick, when in a cold area.
#define BODYTEMP_HEATING_MAX 30 //The maximum number of degrees that your body can heat up in 1 tick, when in a hot area.
#define BODYTEMP_HEAT_DAMAGE_LIMIT 360.15 // The limit the human body can take before it starts taking damage from heat.
@@ -62,10 +71,7 @@
#define PRESSURE_DAMAGE_COEFFICIENT 4 //The amount of pressure damage someone takes is equal to (pressure / HAZARD_HIGH_PRESSURE)*PRESSURE_DAMAGE_COEFFICIENT, with the maximum of MAX_PRESSURE_DAMAGE
#define MAX_HIGH_PRESSURE_DAMAGE 4 //This used to be 20... I got this much random rage for some retarded decision by polymorph?! Polymorph now lies in a pool of blood with a katana jammed in his spleen. ~Errorage --PS: The katana did less than 20 damage to him :(
-#define LOW_PRESSURE_DAMAGE 4 //The amount of damage someone takes when in a low pressure area (The pressure threshold is so low that it doesn't make sense to do any calculations, so it just applies this flat value).
-//low pressure damage bumped to 4 because current ZAS means cold does not proc half the time. 6 was too much.
-#define PRESSURE_SUIT_REDUCTION_COEFFICIENT 0.6 //This is how much (percentual) a suit with the flag STOPSPRESSUREDMAGE reduces pressure. orig. .8 but we had spacewalkers without helmets.
-#define PRESSURE_HEAD_REDUCTION_COEFFICIENT 0.4 //This is how much (percentual) a helmet/hat with the flag STOPSPRESSUREDMAGE reduces pressure.
+#define LOW_PRESSURE_DAMAGE 4 //The amounb of damage someone takes when in a low pressure area (The pressure threshold is so low that it doesn't make sense to do any calculations, so it just applies this flat value).
// Doors!
#define DOOR_CRUSH_DAMAGE 10
@@ -133,6 +139,11 @@
#define T20C 293.15 // 20degC
#define TCMB 2.7 // -270.3degC
+//XGM gas flags
+#define XGM_GAS_FUEL 1
+#define XGM_GAS_OXIDIZER 2
+#define XGM_GAS_CONTAMINANT 4
+
//Used to be used by FEA
//var/turf/space/Space_Tile = locate(/turf/space) // A space tile to reference when atmos wants to remove excess heat.
@@ -261,21 +272,23 @@ var/MAX_EXPLOSION_RANGE = 14
// bitflags for clothing parts
#define HEAD 1
-#define UPPER_TORSO 2
-#define LOWER_TORSO 4
-#define LEG_LEFT 8
-#define LEG_RIGHT 16
-#define LEGS 24
-#define FOOT_LEFT 32
-#define FOOT_RIGHT 64
-#define FEET 96
-#define ARM_LEFT 128
-#define ARM_RIGHT 256
-#define ARMS 384
-#define HAND_LEFT 512
-#define HAND_RIGHT 1024
-#define HANDS 1536
-#define FULL_BODY 2047
+#define FACE 2
+#define EYES 4
+#define UPPER_TORSO 8
+#define LOWER_TORSO 16
+#define LEG_LEFT 32
+#define LEG_RIGHT 64
+#define LEGS 96
+#define FOOT_LEFT 128
+#define FOOT_RIGHT 256
+#define FEET 384
+#define ARM_LEFT 512
+#define ARM_RIGHT 1024
+#define ARMS 1536
+#define HAND_LEFT 2048
+#define HAND_RIGHT 4096
+#define HANDS 6144
+#define FULL_BODY 8191
// bitflags for the percentual amount of protection a piece of clothing which covers the body part offers.
// Used with human/proc/get_heat_protection() and human/proc/get_cold_protection()
@@ -745,8 +758,10 @@ var/list/RESTRICTED_CAMERA_NETWORKS = list( //Those networks can only be accesse
#define IS_SYNTHETIC 16384
//Language flags.
-#define WHITELISTED 1 // Language is available if the speaker is whitelisted.
-#define RESTRICTED 2 // Language can only be accquired by spawning or an admin.
+#define WHITELISTED 1 // Language is available if the speaker is whitelisted.
+#define RESTRICTED 2 // Language can only be accquired by spawning or an admin.
+#define NONVERBAL 4 // Language has a significant non-verbal component. Speech is garbled without line-of-sight
+#define SIGNLANG 8 // Language is completely non-verbal. Speech is displayed through emotes for those who can understand.
//Flags for zone sleeping
#define ZONE_ACTIVE 1
@@ -778,8 +793,74 @@ var/list/RESTRICTED_CAMERA_NETWORKS = list( //Those networks can only be accesse
#define VAMP_FULL 13
+/*
+ Germs and infections
+*/
+
+#define GERM_LEVEL_AMBIENT 110 //maximum germ level you can reach by standing still
+#define GERM_LEVEL_MOVE_CAP 200 //maximum germ level you can reach by running around
+
+#define INFECTION_LEVEL_ONE 100
+#define INFECTION_LEVEL_TWO 500
+#define INFECTION_LEVEL_THREE 1000
+/*
+ Shuttles
+*/
+// these define the time taken for the shuttle to get to SS13
+// and the time before it leaves again
+#define SHUTTLE_PREPTIME 300 // 5 minutes = 300 seconds - after this time, the shuttle departs centcom and cannot be recalled
+#define SHUTTLE_LEAVETIME 180 // 3 minutes = 180 seconds - the duration for which the shuttle will wait at the station after arriving
+#define SHUTTLE_TRANSIT_DURATION 300 // 5 minutes = 300 seconds - how long it takes for the shuttle to get to the station
+#define SHUTTLE_TRANSIT_DURATION_RETURN 120 // 2 minutes = 120 seconds - for some reason it takes less time to come back, go figure.
+
+//Shuttle moving status
+#define SHUTTLE_IDLE 0
+#define SHUTTLE_WARMUP 1
+#define SHUTTLE_INTRANSIT 2
+
+//Ferry shuttle processing status
+#define IDLE_STATE 0
+#define WAIT_LAUNCH 1
+#define WAIT_ARRIVE 2
+#define WAIT_FINISH 3
+
+//computer3 error codes, move lower in the file when it passes dev -Sayu
+ #define PROG_CRASH 1 // Generic crash
+ #define MISSING_PERIPHERAL 2 // Missing hardware
+ #define BUSTED_ASS_COMPUTER 4 // Self-perpetuating error. BAC will continue to crash forever.
+ #define MISSING_PROGRAM 8 // Some files try to automatically launch a program. This is that failing.
+ #define FILE_DRM 16 // Some files want to not be copied/moved. This is them complaining that you tried.
+ #define NETWORK_FAILURE 32
+
+//Some on_mob_life() procs check for alien races.
+#define IS_DIONA 1
+#define IS_VOX 2
+#define IS_SKRELL 3
+#define IS_UNATHI 4
#define MAX_GEAR_COST 5 //Used in chargen for loadout limit.
+
+
+/*
+ Atmos Machinery
+*/
+#define MAX_SIPHON_FLOWRATE 2500 //L/s This can be used to balance how fast a room is siphoned. Anything higher than CELL_VOLUME has no effect.
+#define MAX_SCRUBBER_FLOWRATE 200 //L/s Max flow rate when scrubbing from a turf.
+
+//These balance how easy or hard it is to create huge pressure gradients with pumps and filters. Lower values means it takes longer to create large pressures differences.
+//Has no effect on pumping gasses from high pressure to low, only from low to high. Must be between 0 and 1.
+#define ATMOS_PUMP_EFFICIENCY 2.5
+#define ATMOS_FILTER_EFFICIENCY 2.5
+
+//will not bother pumping or filtering if the gas source as fewer than this amount of moles, to help with performance.
+#define MINUMUM_MOLES_TO_PUMP 0.01
+#define MINUMUM_MOLES_TO_FILTER 0.1
+
+//The flow rate/effectiveness of various atmos devices is limited by their internal volume, so for many atmos devices these will control maximum flow rates in L/s
+#define ATMOS_DEFAULT_VOLUME_PUMP 200 //L
+#define ATMOS_DEFAULT_VOLUME_FILTER 200 //L
+#define ATMOS_DEFAULT_VOLUME_MIXER 200 //L
+#define ATMOS_DEFAULT_VOLUME_PIPE 70 //L
diff --git a/code/stylesheet.dm b/code/stylesheet.dm
index da5ce74a..e1d214d8 100644
--- a/code/stylesheet.dm
+++ b/code/stylesheet.dm
@@ -62,6 +62,7 @@ h1.alert, h2.alert {color: #000000;}
.modooc {color: #184880; font-weight: bold;}
.adminmod {color: #402A14; font-weight: bold;}
.tajaran {color: #803B56;}
+.tajaran_signlang {color: #941C1C;}
.skrell {color: #00CED1;}
.soghun {color: #228B22;}
.vox {color: #AA00AA;}
diff --git a/code/world.dm b/code/world.dm
index 95b6cb5c..ee3c38e6 100644
--- a/code/world.dm
+++ b/code/world.dm
@@ -1,7 +1,7 @@
/world
mob = /mob/new_player
turf = /turf/space
- area = /area
+ area = /area//space
view = "15x15"
cache_lifespan = 0 //stops player uploaded stuff from being kept in the rsc past the current session
@@ -283,7 +283,7 @@ var/world_topic_spam_protect_time = world.timeofday
s += "[station_name()] - Heavy Roleplay";
s += " ("
- s += "" //Change this to wherever you want the hub to link to.
+ s += "" //Change this to wherever you want the hub to link to.
// s += "[game_version]"
s += "Forums" //Replace this with something else. Or ever better, delete it and uncomment the game version.
s += ""
diff --git a/config/names/adjectives.txt b/config/names/adjectives.txt
new file mode 100644
index 00000000..73b6a407
--- /dev/null
+++ b/config/names/adjectives.txt
@@ -0,0 +1,397 @@
+adorable
+adventurous
+aggressive
+alert
+attractive
+average
+beautiful
+blue-eyed
+bloody
+blushing
+bright
+clean
+clear
+cloudy
+colorful
+crowded
+cute
+dark
+drab
+distinct
+dull
+elegant
+excited
+fancy
+filthy
+glamorous
+gleaming
+gorgeous
+graceful
+grotesque
+handsome
+homely
+light
+long
+magnificent
+misty
+motionless
+muddy
+old-fashioned
+plain
+poised
+precious
+quaint
+shiny
+smoggy
+sparkling
+spotless
+stormy
+strange
+ugly
+ugliest
+unsightly
+unusual
+wide-eyed
+alive
+annoying
+bad
+better
+beautiful
+brainy
+breakable
+busy
+careful
+cautious
+clever
+clumsy
+concerned
+crazy
+curious
+dead
+different
+difficult
+doubtful
+easy
+expensive
+famous
+fragile
+frail
+gifted
+helpful
+helpless
+horrible
+important
+impossible
+inexpensive
+innocent
+inquisitive
+modern
+mushy
+odd
+open
+outstanding
+poor
+powerful
+prickly
+puzzled
+real
+rich
+shy
+sleepy
+stupid
+super
+talented
+tame
+tender
+tough
+uninterested
+vast
+wandering
+wild
+wrong
+
+angry
+annoyed
+anxious
+arrogant
+ashamed
+awful
+bad
+bewildered
+black
+blue
+bored
+clumsy
+combative
+condemned
+confused
+crazy,flipped-out
+creepy
+cruel
+dangerous
+defeated
+defiant
+depressed
+disgusted
+disturbed
+dizzy
+dull
+embarrassed
+envious
+evil
+fierce
+foolish
+frantic
+frightened
+grieving
+grumpy
+helpless
+homeless
+hungry
+hurt
+ill
+itchy
+jealous
+jittery
+lazy
+lonely
+mysterious
+nasty
+naughty
+nervous
+nutty
+obnoxious
+outrageous
+panicky
+repulsive
+scary
+selfish
+sore
+tense
+terrible
+testy
+thoughtless
+tired
+troubled
+upset
+uptight
+weary
+wicked
+worried
+agreeable
+amused
+brave
+calm
+charming
+cheerful
+comfortable
+cooperative
+courageous
+delightful
+determined
+eager
+elated
+enchanting
+encouraging
+energetic
+enthusiastic
+excited
+exuberant
+fair
+faithful
+fantastic
+fine
+friendly
+funny
+gentle
+glorious
+good
+happy
+healthy
+helpful
+hilarious
+jolly
+joyous
+kind
+lively
+lovely
+lucky
+nice
+obedient
+perfect
+pleasant
+proud
+relieved
+silly
+smiling
+splendid
+successful
+thankful
+thoughtful
+victorious
+vivacious
+witty
+wonderful
+zealous
+zany
+broad
+chubby
+crooked
+curved
+deep
+flat
+high
+hollow
+low
+narrow
+round
+shallow
+skinny
+square
+steep
+straight
+wide
+big
+colossal
+fat
+gigantic
+great
+huge
+immense
+large
+little
+mammoth
+massive
+miniature
+petite
+puny
+scrawny
+short
+small
+tall
+teeny
+teeny-tiny
+tiny
+cooing
+deafening
+faint
+harsh
+high-pitched
+hissing
+hushed
+husky
+loud
+melodic
+moaning
+mute
+noisy
+purring
+quiet
+raspy
+resonant
+screeching
+shrill
+silent
+soft
+squealing
+thundering
+voiceless
+whispering
+ancient
+brief
+early
+fast
+late
+long
+modern
+old
+old-fashioned
+quick
+rapid
+short
+slow
+swift
+young
+Taste/Touch
+bitter
+delicious
+fresh
+juicy
+ripe
+rotten
+salty
+sour
+spicy
+stale
+sticky
+strong
+sweet
+tart
+tasteless
+tasty
+thirsty
+fluttering
+fuzzy
+greasy
+grubby
+hard
+hot
+icy
+loose
+melted
+nutritious
+plastic
+prickly
+rainy
+rough
+scattered
+shaggy
+shaky
+sharp
+shivering
+silky
+slimy
+slippery
+smooth
+soft
+solid
+steady
+sticky
+tender
+tight
+uneven
+weak
+wet
+wooden
+yummy
+boiling
+breezy
+broken
+bumpy
+chilly
+cold
+cool
+creepy
+crooked
+cuddly
+curly
+damaged
+damp
+dirty
+dry
+dusty
+filthy
+flaky
+fluffy
+freezing
+hot
+warm
+wet
+abundant
+empty
+few
+heavy
+light
+many
+numerous
+substantial
\ No newline at end of file
diff --git a/config/names/ai.txt b/config/names/ai.txt
new file mode 100644
index 00000000..01c65cf1
--- /dev/null
+++ b/config/names/ai.txt
@@ -0,0 +1,147 @@
+1-Rover-1
+16-20
+7-Zark-7
+790
+AM
+AMEE
+ASTAR
+Adaptive Manipulator
+Allied Mastercomputer
+Alpha 5
+Alpha 6
+Alpha 7
+AmigoBot
+Android
+Aniel
+Asimov
+Astor
+B-4
+B-9
+B.O.B.
+B166ER
+Bender
+Bishop
+Blitz
+Box
+Brackenridge
+C-3PO
+Cassandra One
+Cell
+Chii
+Chip
+Computer
+Conky 2000
+Cutie
+Data
+Dee Model
+Deep Thought
+Dor-15
+Dorfl
+Dot Matrix
+Duey
+E.D.I.
+ED-209
+E-Man
+Emma-2
+Erasmus
+Ez-27
+FRIEND COMPUTER
+Fagor
+Faith
+Fi
+Frost
+Fum
+Futura
+G2
+George
+Gnut
+Gort
+H.A.R.L.I.E.
+H.E.L.P.eR.
+H.E.R.B.I.E.
+HAL 9000
+Hadaly
+Huey
+Irona
+Jay-Dub
+Jinx
+Johnny 5
+K-9
+KITT
+Klapaucius
+Kryten 2X4B-523P
+L-76
+L-Ron
+LUH 3417
+Louie
+MARK13
+Maria
+Marvin
+Master Control Program
+Max 404
+Maximillian
+Mechagodzilla
+Mechani-Kong
+Metalhead
+Mr. R.I.N.G.
+NCH
+Necron-99
+Norby
+OMM 0910
+Orange v 3.5
+PTO
+Project 2501
+R.I.C. 2.0
+R2-D2
+R4-P17
+Revelation
+Ro-Man
+Robbie
+S.A.M.
+S.H.O.C.K.
+S.H.R.O.U.D.
+S.O.P.H.I.E.
+SEN 5241
+SHODAN
+SID 6.7
+Setaur
+Shrike
+Solo
+Speedy
+Super 17
+Surgeon General Kraken
+T-1000
+T-800
+T-850
+THX 1138
+TWA
+Terminus
+Tidy
+Tik-Tok
+Tobor
+Trurl
+ULTRABOT
+Ulysses
+Uniblab
+V.I.N.CENT.
+Voltes V
+W1k1
+Wikipedia
+Windows 3.1
+X-5
+XERXES
+XR
+Yod
+Z-1
+Z-2
+Z-3
+Zed
+Zord
+Mugsy3000
+Terminus
+Decimus
+Robot Devil
+Optimus
+Megatron
+Soundwave
+Ironhide
diff --git a/config/names/clown.txt b/config/names/clown.txt
new file mode 100644
index 00000000..c29fca74
--- /dev/null
+++ b/config/names/clown.txt
@@ -0,0 +1,37 @@
+Gigglesworth
+Honkel the III
+Goose McSunny
+Mr. Shoe
+Toodles Sharperton
+Dinky Doodle
+Honkerbelle
+Bo Bo Sassy
+Baby Cakes
+Ladybug Honks
+Ziggy Yoyo
+Razzle Dazzle
+Buster Frown
+Pepinpop
+Silly Willy
+Jo Jo Bobo Bo
+Pocket
+Patches
+Checkers
+Freckle
+Honker
+Bonker
+Skiddle
+Scootaloo
+Sprinkledinkle
+Ronnie Pace
+Miss Stockings
+Slippy Joe
+Redshirt McBeat
+Flop O'Honker
+Speckles
+Bubble
+Button
+Sparkle
+Giggles
+Jingle
+Candy
\ No newline at end of file
diff --git a/config/names/death_commando.txt b/config/names/death_commando.txt
new file mode 100644
index 00000000..b10e181b
--- /dev/null
+++ b/config/names/death_commando.txt
@@ -0,0 +1,70 @@
+Killiam Shakespeare
+Stabby McGee
+Sgt. Slaughter
+Maxx Power
+Sir Killaslot
+Slab Bulkhead
+Fridge Largemeat
+Punt Speedchunk
+Butch Deadlift
+Bold Bigflank
+Splint Chesthair
+Flint Ironstag
+Bolt Vanderhuge
+Thick McRunfast
+Blast Hardcheese
+Buff Drinklots
+Trunk Slamchest
+Fist Rockbone
+Stump Beefgnaw
+Smash Lampjaw
+Punch Rockgroin
+Buck Plankchest
+Stump Chunkman
+Dirk Hardpeck
+Rip Steakface
+Slate Slabrock
+Crud Bonemeal
+Brick Hardmeat
+Rip Sidecheek
+Punch Sideiron
+Gristle McThornBody
+Slake Fistcrunch
+Buff Hardback
+Blast Thickneck
+Crunch Buttsteak
+Slab Squatthrust
+Lump Beefrock
+Touch Rustrod
+Reef Blastbody
+Smoke Manmuscle
+Beat Punchbeef
+Pack Blowfist
+Roll Fizzlebeef
+Lance Killiam
+George Melons
+Maximilian Murderface
+Bob Johnson
+Crush McStompbones
+Hank Chesthair
+Killing McKillingalot
+Mancrush McBrorape
+Rex Dudekiller VII
+Seamus McTosterone
+Hans Testosteroneson
+Max Pain
+Theodore Pain
+Sarah Pain
+GORE Vidal
+Leonardo Da Viking
+Noam Bombsky
+Al "Otta" Gore
+Gibbs McLargehuge
+Evil Martin Luther King
+Evil Bob Marley
+Duke Killington
+AMERICA
+Toolboxl Rose
+Zombie Gandhi
+A whole bunch of spiders in a SWAT suit
+THAT DAMN FAGGOT TRAITOR GEORGE MELONS
\ No newline at end of file
diff --git a/config/names/first.txt b/config/names/first.txt
new file mode 100644
index 00000000..a33e1811
--- /dev/null
+++ b/config/names/first.txt
@@ -0,0 +1,1502 @@
+Abel
+Adolph
+Aida
+Alan
+Alden
+Alex
+Alexa
+Alexandria
+Alexis
+Alexus
+Alfred
+Alfreda
+Alger
+Alisa
+Alisya
+Allegra
+Allegria
+Allen
+Alma
+Alysha
+Alyssia
+Amaryllis
+Ambrosine
+Amos
+Angel
+Anjelica
+Anne
+Arabella
+Archie
+Arielle
+Arleen
+Arn
+Art
+Ashlie
+Astor
+Aubrey
+Avalon
+Averill
+Baldric
+Barbra
+Bartholomew
+Beckah
+Becky
+Bernice
+Bertrand
+Bethney
+Betsy
+Bidelia
+Bill
+Blake
+Brayden
+Breanne
+Brendan
+Brittani
+Bronte
+Brooke
+Bryce
+Burt
+Byrne
+Byron
+Bysshe
+Cadence
+Calanthia
+Caleigh
+Camryn
+Candace
+Candice
+Candis
+Canute
+Carly
+Carlyle
+Carolyn
+Carry
+Carter
+Caryl
+Casimir
+Cassian
+Cecily
+Charlton
+Cherette
+Cheri
+Cherry
+Chip
+Christa
+Christiana
+Christobel
+Claribel
+Clark
+Claudius
+Clement
+Cleveland
+Cliff
+Clinton
+Clitus
+Clover
+Collin
+Coreen
+Corrine
+Cy
+Cynthia
+Dalya
+Damian
+Daniella
+Danny
+Darcey
+Darell
+Daria
+Darin
+Dayna
+Deangelo
+Debbi
+Dee
+Deena
+Della
+Delma
+Denholm
+Denys
+Desmond
+Devin
+Diamond
+Dina
+Dolores
+Dominic
+Donella
+Donna
+Donny
+Dorothy
+Dortha
+Driscoll
+Duncan
+Easter
+Ebba
+Edgar
+Effie
+Eliot
+Eliott
+Elizabeth
+Elle
+Elric
+Elspet
+Elwood
+Emma
+Emmanuel
+Ermintrude
+Esmeralda
+Eugenia
+Euphemia
+Eustace
+Eveleen
+Evelina
+Fay
+Fitz
+Flick
+Floella
+Flora
+Flossie
+Fortune
+Francis
+Frankie
+Fulton
+Garret
+Gaye
+Gaylord
+Genette
+Georgene
+Geraldine
+Gervase
+Gina
+Ginger
+Gladwyn
+Glenna
+Goddard
+Godwin
+Goodwin
+Gordon
+Graeme
+Gratian
+Greta
+Griselda
+Gwenda
+Gwenevere
+Hadley
+Haidee
+Hailey
+Hal
+Haleigh
+Happy
+Hartley
+Hayley
+Heather
+Hedley
+Helen
+Henderson
+Hepsie
+Hervey
+Holden
+Homer
+Hope
+Horatio
+Hortensia
+Huffie
+Hugo
+Iantha
+Ileen
+Innocent
+Irene
+Irvine
+Jacaline
+Jacquetta
+Jacqui
+Jake
+Jakki
+Jalen
+Jamar
+Jamie
+Jamison
+Janel
+Janelle
+Janette
+Janie
+Janina
+Janine
+Jasmine
+Jaydon
+Jaye
+Jaylee
+Jayne
+Jaynie
+Jeanna
+Jeannie
+Jeannine
+Jeb
+Jed
+Jemmy
+Jenifer
+Jennie
+Jera
+Jere
+Jeri
+Jermaine
+Jerrie
+Jillian
+Jillie
+Jim
+Joachim
+Joetta
+Joey
+Johnathan
+Johnny
+Joi
+Jonathon
+Joni
+Josepha
+Josh
+Josiah
+Joye
+Julia
+July
+Kaelea
+Kaleigh
+Karenza
+Karly
+Karyn
+Kat
+Kathy
+Katlyn
+Kayleigh
+Keegan
+Keira
+Keith
+Kellie
+Kennard
+Kerena
+Kerensa
+Keturah
+Keziah
+Kimberley
+Lacy
+Lakeisha
+Lalla
+Lanny
+Latanya
+Launce
+Laurencia
+Laurissa
+Leeann
+Leia
+Leland
+Lennox
+Leroi
+Lessie
+Leta
+Lexia
+Lexus
+Linden
+Lindsie
+Lindy
+Linton
+Lockie
+Loreto
+Lori
+Lorin
+Lou
+Luanne
+Lucian
+Luvenia
+Lyndsey
+Lynn
+Lynsey
+Lynwood
+Mabelle
+Macey
+Madyson
+Maegan
+Malachi
+Malcolm
+Manley
+Marcia
+Mariabella
+Marilene
+Marion
+Marion
+Marje
+Marjory
+Marlowe
+Marlyn
+Marshall
+Maryann
+Maudie
+Maurene
+May
+Maynard
+Melvyn
+Merideth
+Merrilyn
+Meryl
+Micheal
+Mike
+Milton
+Minnie
+Monna
+Montague
+Monte
+Monty
+Muriel
+Mya
+Myriam
+Myrtie
+Nan
+Nathaniel
+Nelle
+Nena
+Nerissa
+Netta
+Nettie
+Nikolas
+Noah
+Nonie
+Nova
+Nowell
+Nydia
+Olive
+Oralie
+Osbert
+Osborn
+Osborne
+Osmund
+Paget
+Patience
+Patrick
+Patton
+Pauleen
+Pene
+Percival
+Peregrine
+Pheobe
+Phoebe
+Phyliss
+Phyllida
+Phyllis
+Porsche
+Prosper
+Prue
+Quanah
+Quiana
+Raelene
+Rain
+Randa
+Randal
+Rastus
+Rayner
+Rebeckah
+Reene
+Renie
+Reuben
+Rexana
+Reynard
+Rhetta
+Rich
+Richie
+Rick
+Rickena
+Rickey
+Rickie
+Rodger
+Roger
+Romayne
+Ronnette
+Roscoe
+Rosemary
+Roswell
+Royce
+Rubye
+Rusty
+Sabella
+Sachie
+Sal
+Sally
+Saranna
+Sawyer
+Scotty
+Seneca
+Seymour
+Shan
+Shana
+Shanika
+Shannah
+Shannon
+Shantae
+Sharalyn
+Sharla
+Sheri
+Sherie
+Sherill
+Sherri
+Shiloh
+Simon
+Sissy
+Sloan
+Sophie
+Sorrel
+Spike
+Star
+Steph
+Stephany
+Sue
+Sukie
+Sunshine
+Susanna
+Susannah
+Suzan
+Suzy
+Sybil
+Syd
+Sydney
+Tamika
+Tamsin
+Tania
+Tansy
+Tatyanna
+Taylor
+Tel
+Terrell
+Tiffany
+Tod
+Tolly
+Topaz
+Tori
+Tracee
+Tracey
+Trinity
+Tye
+Uland
+Ulric
+Ulyssa
+Valary
+Vaughn
+Verna
+Vince
+Vinnie
+Vivyan
+Walter
+Ward
+Warner
+Wayne
+Wendi
+Whitaker
+William
+Willy
+Winifred
+Wisdom
+Woodrow
+Woody
+Wynonna
+Wynter
+Yasmin
+Yolanda
+Ysabel
+Zack
+Zeke
+Zelda
+Zune
+Jacob
+Michael
+Ethan
+Joshua
+Daniel
+Alexander
+Anthony
+William
+Christopher
+Matthew
+Jayden
+Andrew
+Joseph
+David
+Noah
+Aiden
+James
+Ryan
+Logan
+John
+Nathan
+Elijah
+Christian
+Gabriel
+Benjamin
+Jonathan
+Tyler
+Samuel
+Nicholas
+Gavin
+Dylan
+Jackson
+Brandon
+Caleb
+Mason
+Angel
+Isaac
+Evan
+Jack
+Kevin
+Jose
+Isaiah
+Luke
+Landon
+Justin
+Lucas
+Zachary
+Jordan
+Robert
+Aaron
+Brayden
+Thomas
+Cameron
+Hunter
+Austin
+Adrian
+Connor
+Owen
+Aidan
+Jason
+Julian
+Wyatt
+Charles
+Luis
+Carter
+Juan
+Chase
+Diego
+Jeremiah
+Brody
+Xavier
+Adam
+Carlos
+Sebastian
+Liam
+Hayden
+Nathaniel
+Henry
+Jesus
+Ian
+Tristan
+Bryan
+Sean
+Cole
+Alex
+Eric
+Brian
+Jaden
+Carson
+Blake
+Ayden
+Cooper
+Dominic
+Brady
+Caden
+Josiah
+Kyle
+Colton
+Kaden
+Eli
+Miguel
+Antonio
+Parker
+Steven
+Alejandro
+Riley
+Richard
+Timothy
+Devin
+Jesse
+Victor
+Jake
+Joel
+Colin
+Kaleb
+Bryce
+Levi
+Oliver
+Oscar
+Vincent
+Ashton
+Cody
+Micah
+Preston
+Marcus
+Max
+Patrick
+Seth
+Jeremy
+Peyton
+Nolan
+Ivan
+Damian
+Maxwell
+Alan
+Kenneth
+Jonah
+Jorge
+Mark
+Giovanni
+Eduardo
+Grant
+Collin
+Gage
+Omar
+Emmanuel
+Trevor
+Edward
+Ricardo
+Cristian
+Nicolas
+Kayden
+George
+Jaxon
+Paul
+Braden
+Elias
+Andres
+Derek
+Garrett
+Tanner
+Malachi
+Conner
+Fernando
+Cesar
+Javier
+Miles
+Jaiden
+Alexis
+Leonardo
+Santiago
+Francisco
+Cayden
+Shane
+Edwin
+Hudson
+Travis
+Bryson
+Erick
+Jace
+Hector
+Josue
+Peter
+Jaylen
+Mario
+Manuel
+Abraham
+Grayson
+Damien
+Kaiden
+Spencer
+Stephen
+Edgar
+Wesley
+Shawn
+Trenton
+Jared
+Jeffrey
+Landen
+Johnathan
+Bradley
+Braxton
+Ryder
+Camden
+Roman
+Asher
+Brendan
+Maddox
+Sergio
+Israel
+Andy
+Lincoln
+Erik
+Donovan
+Raymond
+Avery
+Rylan
+Dalton
+Harrison
+Andre
+Martin
+Keegan
+Marco
+Jude
+Sawyer
+Dakota
+Leo
+Calvin
+Kai
+Drake
+Troy
+Zion
+Clayton
+Roberto
+Zane
+Gregory
+Tucker
+Rafael
+Kingston
+Dominick
+Ezekiel
+Griffin
+Devon
+Drew
+Lukas
+Johnny
+Ty
+Pedro
+Tyson
+Caiden
+Mateo
+Braylon
+Cash
+Aden
+Chance
+Taylor
+Marcos
+Maximus
+Ruben
+Emanuel
+Simon
+Corbin
+Brennan
+Dillon
+Skyler
+Myles
+Xander
+Jaxson
+Dawson
+Kameron
+Kyler
+Axel
+Colby
+Jonas
+Joaquin
+Payton
+Brock
+Frank
+Enrique
+Quinn
+Emilio
+Malik
+Grady
+Angelo
+Julio
+Derrick
+Raul
+Fabian
+Corey
+Gerardo
+Dante
+Ezra
+Armando
+Allen
+Theodore
+Gael
+Amir
+Zander
+Adan
+Maximilian
+Randy
+Easton
+Dustin
+Luca
+Phillip
+Julius
+Charlie
+Ronald
+Jakob
+Cade
+Brett
+Trent
+Silas
+Keith
+Emiliano
+Trey
+Jalen
+Darius
+Lane
+Jerry
+Jaime
+Scott
+Graham
+Weston
+Braydon
+Anderson
+Rodrigo
+Pablo
+Saul
+Danny
+Donald
+Elliot
+Brayan
+Dallas
+Lorenzo
+Casey
+Mitchell
+Alberto
+Tristen
+Rowan
+Jayson
+Gustavo
+Aaden
+Amari
+Dean
+Braeden
+Declan
+Chris
+Ismael
+Dane
+Louis
+Arturo
+Brenden
+Felix
+Jimmy
+Cohen
+Tony
+Holden
+Reid
+Abel
+Bennett
+Zackary
+Arthur
+Nehemiah
+Ricky
+Esteban
+Cruz
+Finn
+Mauricio
+Dennis
+Keaton
+Albert
+Marvin
+Mathew
+Larry
+Moises
+Issac
+Philip
+Quentin
+Curtis
+Greyson
+Jameson
+Everett
+Jayce
+Darren
+Elliott
+Uriel
+Alfredo
+Hugo
+Alec
+Jamari
+Marshall
+Walter
+Judah
+Jay
+Lance
+Beau
+Ali
+Landyn
+Yahir
+Phoenix
+Nickolas
+Kobe
+Bryant
+Maurice
+Russell
+Leland
+Colten
+Reed
+Davis
+Joe
+Ernesto
+Desmond
+Kade
+Reece
+Morgan
+Ramon
+Rocco
+Orlando
+Ryker
+Brodie
+Paxton
+Jacoby
+Douglas
+Kristopher
+Gary
+Lawrence
+Izaiah
+Solomon
+Nikolas
+Mekhi
+Justice
+Tate
+Jaydon
+Salvador
+Shaun
+Alvin
+Eddie
+Kane
+Davion
+Zachariah
+Dorian
+Titus
+Kellen
+Camron
+Isiah
+Javon
+Nasir
+Milo
+Johan
+Byron
+Jasper
+Jonathon
+Chad
+Marc
+Kelvin
+Chandler
+Sam
+Cory
+Deandre
+River
+Reese
+Roger
+Quinton
+Talon
+Romeo
+Franklin
+Noel
+Alijah
+Guillermo
+Gunner
+Damon
+Jadon
+Emerson
+Micheal
+Bruce
+Terry
+Kolton
+Melvin
+Beckett
+Porter
+August
+Brycen
+Dayton
+Jamarion
+Leonel
+Karson
+Zayden
+Keagan
+Carl
+Khalil
+Cristopher
+Nelson
+Braiden
+Moses
+Isaias
+Roy
+Triston
+Walker
+Kale
+Emma
+Isabella
+Emily
+Madison
+Ava
+Olivia
+Sophia
+Abigail
+Elizabeth
+Chloe
+Samantha
+Addison
+Natalie
+Mia
+Alexis
+Alyssa
+Hannah
+Ashley
+Ella
+Sarah
+Grace
+Taylor
+Brianna
+Lily
+Hailey
+Anna
+Victoria
+Kayla
+Lillian
+Lauren
+Kaylee
+Allison
+Savannah
+Nevaeh
+Gabriella
+Sofia
+Makayla
+Avery
+Riley
+Julia
+Leah
+Aubrey
+Jasmine
+Audrey
+Katherine
+Morgan
+Brooklyn
+Destiny
+Sydney
+Alexa
+Kylie
+Brooke
+Kaitlyn
+Evelyn
+Layla
+Madeline
+Kimberly
+Zoe
+Jessica
+Peyton
+Alexandra
+Claire
+Madelyn
+Maria
+Mackenzie
+Arianna
+Jocelyn
+Amelia
+Angelina
+Trinity
+Andrea
+Maya
+Valeria
+Sophie
+Rachel
+Vanessa
+Aaliyah
+Mariah
+Gabrielle
+Katelyn
+Ariana
+Bailey
+Camila
+Jennifer
+Melanie
+Gianna
+Charlotte
+Paige
+Autumn
+Payton
+Faith
+Sara
+Isabelle
+Caroline
+Genesis
+Isabel
+Mary
+Zoey
+Gracie
+Megan
+Haley
+Mya
+Michelle
+Molly
+Stephanie
+Nicole
+Jenna
+Natalia
+Sadie
+Jada
+Serenity
+Lucy
+Ruby
+Eva
+Kennedy
+Rylee
+Jayla
+Naomi
+Rebecca
+Lydia
+Daniela
+Bella
+Keira
+Adriana
+Lilly
+Hayden
+Miley
+Katie
+Jade
+Jordan
+Gabriela
+Amy
+Angela
+Melissa
+Valerie
+Giselle
+Diana
+Amanda
+Kate
+Laila
+Reagan
+Jordyn
+Kylee
+Danielle
+Briana
+Marley
+Leslie
+Kendall
+Catherine
+Liliana
+Mckenzie
+Jacqueline
+Ashlyn
+Reese
+Marissa
+London
+Juliana
+Shelby
+Cheyenne
+Angel
+Daisy
+Makenzie
+Miranda
+Erin
+Amber
+Alana
+Ellie
+Breanna
+Ana
+Mikayla
+Summer
+Piper
+Adrianna
+Jillian
+Sierra
+Jayden
+Sienna
+Alicia
+Lila
+Margaret
+Alivia
+Brooklynn
+Karen
+Violet
+Sabrina
+Stella
+Aniyah
+Annabelle
+Alexandria
+Kathryn
+Skylar
+Aliyah
+Delilah
+Julianna
+Kelsey
+Khloe
+Carly
+Amaya
+Mariana
+Christina
+Alondra
+Tessa
+Eliana
+Bianca
+Jazmin
+Clara
+Vivian
+Josephine
+Delaney
+Scarlett
+Elena
+Cadence
+Alexia
+Maggie
+Laura
+Nora
+Ariel
+Elise
+Nadia
+Mckenna
+Chelsea
+Lyla
+Alaina
+Jasmin
+Hope
+Leila
+Caitlyn
+Cassidy
+Makenna
+Allie
+Izabella
+Eden
+Callie
+Haylee
+Caitlin
+Kendra
+Karina
+Kyra
+Kayleigh
+Addyson
+Kiara
+Jazmine
+Karla
+Camryn
+Alina
+Lola
+Kyla
+Kelly
+Fatima
+Tiffany
+Kira
+Crystal
+Mallory
+Esmeralda
+Alejandra
+Eleanor
+Angelica
+Jayda
+Abby
+Kara
+Veronica
+Carmen
+Jamie
+Ryleigh
+Valentina
+Allyson
+Dakota
+Kamryn
+Courtney
+Cecilia
+Madeleine
+Aniya
+Alison
+Esther
+Heaven
+Aubree
+Lindsey
+Leilani
+Nina
+Melody
+Macy
+Ashlynn
+Joanna
+Cassandra
+Alayna
+Kaydence
+Madilyn
+Aurora
+Heidi
+Emerson
+Kimora
+Madalyn
+Erica
+Josie
+Katelynn
+Guadalupe
+Harper
+Ivy
+Lexi
+Camille
+Savanna
+Dulce
+Daniella
+Lucia
+Emely
+Joselyn
+Kiley
+Kailey
+Miriam
+Cynthia
+Rihanna
+Georgia
+Rylie
+Harmony
+Kiera
+Kyleigh
+Monica
+Bethany
+Kaylie
+Cameron
+Teagan
+Cora
+Brynn
+Ciara
+Genevieve
+Alice
+Maddison
+Eliza
+Tatiana
+Jaelyn
+Erika
+Ximena
+April
+Marely
+Julie
+Danica
+Presley
+Brielle
+Julissa
+Angie
+Iris
+Brenda
+Hazel
+Rose
+Malia
+Shayla
+Fiona
+Phoebe
+Nayeli
+Paola
+Kaelyn
+Selena
+Audrina
+Rebekah
+Carolina
+Janiyah
+Michaela
+Penelope
+Janiya
+Anastasia
+Adeline
+Ruth
+Sasha
+Denise
+Holly
+Madisyn
+Hanna
+Tatum
+Marlee
+Nataly
+Helen
+Janelle
+Lizbeth
+Serena
+Anya
+Jaslene
+Kaylin
+Jazlyn
+Nancy
+Lindsay
+Desiree
+Hayley
+Itzel
+Imani
+Madelynn
+Asia
+Kadence
+Madyson
+Talia
+Jane
+Kayden
+Annie
+Amari
+Bridget
+Raegan
+Jadyn
+Celeste
+Jimena
+Luna
+Yasmin
+Emilia
+Annika
+Estrella
+Sarai
+Lacey
+Ayla
+Alessandra
+Willow
+Nyla
+Dayana
+Lilah
+Lilliana
+Natasha
+Hadley
+Harley
+Priscilla
+Claudia
+Allisson
+Baylee
+Brenna
+Brittany
+Skyler
+Fernanda
+Danna
+Melany
+Cali
+Lia
+Macie
+Lyric
+Logan
+Gloria
+Lana
+Mylee
+Cindy
+Lilian
+Amira
+Anahi
+Alissa
+Anaya
+Lena
+Ainsley
+Sandra
+Noelle
+Marisol
+Meredith
+Kailyn
+Lesly
+Johanna
+Diamond
+Evangeline
+Juliet
+Kathleen
+Meghan
+Paisley
+Athena
+Hailee
+Rosa
+Wendy
+Emilee
+Sage
+Alanna
+Elaina
+Cara
+Nia
+Paris
+Casey
+Dana
+Emery
+Rowan
+Aubrie
+Kaitlin
+Jaden
+Kenzie
+Kiana
+Viviana
+Norah
+Lauryn
+Perla
+Amiyah
+Alyson
+Rachael
+Shannon
+Aileen
+Miracle
+Lillie
+Danika
+Heather
+Kassidy
+Taryn
+Tori
+Francesca
+Kristen
+Amya
+Elle
+Kristina
+Cheyanne
+Haylie
+Patricia
+Anne
+Samara
diff --git a/config/names/first_female.txt b/config/names/first_female.txt
new file mode 100644
index 00000000..69057409
--- /dev/null
+++ b/config/names/first_female.txt
@@ -0,0 +1,807 @@
+Aida
+Alexa
+Alexandria
+Alexis
+Alexus
+Alfreda
+Alisa
+Alisya
+Allegra
+Allegria
+Alma
+Alysha
+Alyssia
+Amaryllis
+Ambrosine
+Angel
+Anjelica
+Anne
+Arabella
+Arielle
+Arleen
+Ashlie
+Astor
+Aubrey
+Avalona
+Averill
+Barbara
+Beckah
+Becky
+Bernice
+Bethney
+Betsy
+Bidelia
+Breanne
+Brittani
+Brooke
+Cadence
+Calanthia
+Caleigh
+Candace
+Candice
+Carly
+Carlyle
+Carolyn
+Carry
+Caryl
+Cecily
+Cherette
+Cheri
+Cherry
+Christa
+Christiana
+Christobelle
+Claribel
+Clover
+Coreen
+Corrine
+Cynthia
+Dalya
+Daniella
+Daria
+Dayna
+Debbi
+Dee
+Deena
+Della
+Delma
+Denys
+Diamond
+Dina
+Dolores
+Donella
+Donna
+Dorothy
+Dortha
+Easter
+Ebba
+Effie
+Elizabeth
+Elle
+Emma
+Ermintrude
+Esmeralda
+Eugenia
+Euphemia
+Eustace
+Eveleen
+Evelina
+Fay
+Floella
+Flora
+Flossie
+Fortune
+Genette
+Georgene
+Geraldine
+Gervase
+Gina
+Ginger
+Gladwyn
+Glenna
+Greta
+Griselda
+Gwenda
+Gwenevere
+Hadley
+Haidee
+Hailey
+Hal
+Haleigh
+Hayley
+Heather
+Hedley
+Helen
+Hepsie
+Hortensia
+Iantha
+Ileen
+Innocent
+Irene
+Jacaline
+Jacquetta
+Jacqui
+Jakki
+Jalen
+Janelle
+Janette
+Janie
+Janina
+Janine
+Jasmine
+Jaylee
+Jaynie
+Jeanna
+Jeannie
+Jeannine
+Jenifer
+Jennie
+Jera
+Jere
+Jeri
+Jillian
+Jillie
+Joetta
+Joi
+Joni
+Josepha
+Joye
+Julia
+July
+Kaelea
+Kaleigh
+Karenza
+Karly
+Karyn
+Kat
+Kathy
+Katlyn
+Kayleigh
+Keegan
+Keira
+Keith
+Kellie
+Kerena
+Kerensa
+Keturah
+Kimberley
+Lacy
+Lakeisha
+Lalla
+Latanya
+Laurencia
+Laurissa
+Leeann
+Leia
+Lessie
+Leta
+Lexia
+Lexus
+Lindsie
+Lindy
+Lockie
+Lori
+Lorin
+Luanne
+Lucian
+Luvenia
+Lyndsey
+Lynn
+Lynsey
+Lynwood
+Mabelle
+Macey
+Madyson
+Maegan
+Marcia
+Mariabella
+Marilene
+Marion
+Marje
+Marjory
+Marlowe
+Marlyn
+Marshall
+Maryann
+Maudie
+Maurene
+May
+Merideth
+Merrilyn
+Meryl
+Minnie
+Monna
+Muriel
+Mya
+Myriam
+Myrtie
+Nan
+Nelle
+Nena
+Nerissa
+Netta
+Nettie
+Nonie
+Nova
+Nowell
+Nydia
+Olive
+Oralie
+Patience
+Pauleen
+Pene
+Peregrine
+Pheobe
+Phoebe
+Phyliss
+Phyllida
+Phyllis
+Porsche
+Prosper
+Prue
+Quanah
+Quiana
+Raelene
+Rain
+Randa
+Randal
+Rebeckah
+Reene
+Renie
+Rexana
+Rhetta
+Ronnette
+Rosemary
+Rubye
+Sabella
+Sachie
+Sally
+Saranna
+Seneca
+Shana
+Shanika
+Shannah
+Shannon
+Shantae
+Sharalyn
+Sharla
+Sheri
+Sherie
+Sherill
+Sherri
+Sissy
+Sophie
+Star
+Steph
+Stephany
+Sue
+Sukie
+Sunshine
+Susanna
+Susannah
+Suzan
+Suzy
+Sydney
+Tamika
+Tania
+Tansy
+Tatyanna
+Tiffany
+Tolly
+Topaz
+Tori
+Tracee
+Tracey
+Ulyssa
+Valary
+Verna
+Vinnie
+Vivyan
+Wendi
+Wisdom
+Wynonna
+Wynter
+Yasmin
+Yolanda
+Ysabel
+Zelda
+Zune
+Emma
+Isabella
+Emily
+Madison
+Ava
+Olivia
+Sophia
+Abigail
+Elizabeth
+Chloe
+Samantha
+Addison
+Natalie
+Mia
+Alexis
+Alyssa
+Hannah
+Ashley
+Ella
+Sarah
+Grace
+Taylor
+Brianna
+Lily
+Hailey
+Anna
+Victoria
+Kayla
+Lillian
+Lauren
+Kaylee
+Allison
+Savannah
+Nevaeh
+Gabriella
+Sofia
+Makayla
+Avery
+Riley
+Julia
+Leah
+Aubrey
+Jasmine
+Audrey
+Katherine
+Morgan
+Brooklyn
+Destiny
+Sydney
+Alexa
+Kylie
+Brooke
+Kaitlyn
+Evelyn
+Layla
+Madeline
+Kimberly
+Zoe
+Jessica
+Peyton
+Alexandra
+Claire
+Madelyn
+Maria
+Mackenzie
+Arianna
+Jocelyn
+Amelia
+Angelina
+Trinity
+Andrea
+Maya
+Valeria
+Sophie
+Rachel
+Vanessa
+Aaliyah
+Mariah
+Gabrielle
+Katelyn
+Ariana
+Bailey
+Camila
+Jennifer
+Melanie
+Gianna
+Charlotte
+Paige
+Autumn
+Payton
+Faith
+Sara
+Isabelle
+Caroline
+Isabel
+Mary
+Zoey
+Gracie
+Megan
+Haley
+Mya
+Michelle
+Molly
+Stephanie
+Nicole
+Jenna
+Natalia
+Sadie
+Jada
+Serenity
+Lucy
+Ruby
+Eva
+Kennedy
+Rylee
+Jayla
+Naomi
+Rebecca
+Lydia
+Daniela
+Bella
+Keira
+Adriana
+Lilly
+Hayden
+Miley
+Katie
+Jade
+Jordan
+Gabriela
+Amy
+Angela
+Melissa
+Valerie
+Giselle
+Diana
+Amanda
+Kate
+Laila
+Reagan
+Jordyn
+Kylee
+Danielle
+Briana
+Marley
+Leslie
+Kendall
+Catherine
+Liliana
+Mckenzie
+Jacqueline
+Ashlyn
+Reese
+Marissa
+London
+Juliana
+Shelby
+Cheyenne
+Angel
+Daisy
+Makenzie
+Miranda
+Erin
+Amber
+Alana
+Ellie
+Breanna
+Ana
+Mikayla
+Summer
+Piper
+Adrianna
+Jillian
+Sierra
+Jayden
+Sienna
+Alicia
+Lila
+Margaret
+Alivia
+Brooklynn
+Karen
+Violet
+Sabrina
+Stella
+Aniyah
+Annabelle
+Alexandria
+Kathryn
+Skylar
+Aliyah
+Delilah
+Julianna
+Kelsey
+Khloe
+Carly
+Amaya
+Mariana
+Christina
+Alondra
+Tessa
+Eliana
+Bianca
+Jazmin
+Clara
+Vivian
+Josephine
+Delaney
+Scarlett
+Elena
+Cadence
+Alexia
+Maggie
+Laura
+Nora
+Ariel
+Elise
+Nadia
+Mckenna
+Chelsea
+Lyla
+Alaina
+Jasmin
+Hope
+Leila
+Caitlyn
+Cassidy
+Makenna
+Allie
+Izabella
+Eden
+Callie
+Haylee
+Caitlin
+Kendra
+Karina
+Kyra
+Kayleigh
+Addyson
+Kiara
+Jazmine
+Karla
+Camryn
+Alina
+Lola
+Kyla
+Kelly
+Fatima
+Tiffany
+Kira
+Crystal
+Mallory
+Esmeralda
+Alejandra
+Eleanor
+Angelica
+Jayda
+Abby
+Kara
+Veronica
+Carmen
+Jamie
+Ryleigh
+Valentina
+Allyson
+Dakota
+Kamryn
+Courtney
+Cecilia
+Madeleine
+Aniya
+Alison
+Esther
+Heaven
+Aubree
+Lindsey
+Leilani
+Nina
+Melody
+Macy
+Ashlynn
+Joanna
+Cassandra
+Alayna
+Kaydence
+Madilyn
+Aurora
+Heidi
+Emerson
+Kimora
+Madalyn
+Erica
+Josie
+Katelynn
+Guadalupe
+Harper
+Ivy
+Lexi
+Camille
+Savanna
+Dulce
+Daniella
+Lucia
+Emely
+Joselyn
+Kiley
+Kailey
+Miriam
+Cynthia
+Rihanna
+Georgia
+Rylie
+Harmony
+Kiera
+Kyleigh
+Monica
+Bethany
+Kaylie
+Cameron
+Teagan
+Cora
+Brynn
+Ciara
+Genevieve
+Alice
+Maddison
+Eliza
+Tatiana
+Jaelyn
+Erika
+Ximena
+April
+Marely
+Julie
+Danica
+Presley
+Brielle
+Julissa
+Angie
+Iris
+Brenda
+Hazel
+Rose
+Malia
+Shayla
+Fiona
+Phoebe
+Nayeli
+Paola
+Kaelyn
+Selena
+Audrina
+Rebekah
+Carolina
+Janiyah
+Michaela
+Penelope
+Janiya
+Anastasia
+Adeline
+Ruth
+Sasha
+Denise
+Holly
+Madisyn
+Hanna
+Tatum
+Marlee
+Nataly
+Helen
+Janelle
+Lizbeth
+Serena
+Anya
+Jaslene
+Kaylin
+Jazlyn
+Nancy
+Lindsay
+Desiree
+Hayley
+Itzel
+Imani
+Madelynn
+Asia
+Kadence
+Madyson
+Talia
+Jane
+Kayden
+Annie
+Amari
+Bridget
+Raegan
+Jadyn
+Celeste
+Jimena
+Luna
+Yasmin
+Emilia
+Annika
+Estrella
+Sarai
+Lacey
+Ayla
+Alessandra
+Willow
+Nyla
+Dayana
+Lilah
+Lilliana
+Natasha
+Hadley
+Harley
+Priscilla
+Claudia
+Allisson
+Baylee
+Brenna
+Brittany
+Skyler
+Fernanda
+Danna
+Melany
+Cali
+Lia
+Macie
+Lyric
+Logan
+Gloria
+Lana
+Mylee
+Cindy
+Lilian
+Amira
+Anahi
+Alissa
+Anaya
+Lena
+Ainsley
+Sandra
+Noelle
+Marisol
+Meredith
+Kailyn
+Lesly
+Johanna
+Diamond
+Evangeline
+Juliet
+Kathleen
+Meghan
+Paisley
+Athena
+Hailee
+Rosa
+Wendy
+Emilee
+Sage
+Alanna
+Elaina
+Cara
+Nia
+Paris
+Casey
+Dana
+Emery
+Rowan
+Aubrie
+Kaitlin
+Jaden
+Kenzie
+Kiana
+Viviana
+Norah
+Lauryn
+Perla
+Amiyah
+Alyson
+Rachael
+Shannon
+Aileen
+Miracle
+Lillie
+Danika
+Heather
+Kassidy
+Taryn
+Tori
+Francesca
+Kristen
+Amya
+Elle
+Kristina
+Cheyanne
+Haylie
+Patricia
+Anne
+Samara
\ No newline at end of file
diff --git a/config/names/first_male.txt b/config/names/first_male.txt
new file mode 100644
index 00000000..ebb8f6d2
--- /dev/null
+++ b/config/names/first_male.txt
@@ -0,0 +1,725 @@
+Abel
+Adolph
+Alan
+Alden
+Alex
+Alfred
+Alger
+Allen
+Amos
+Apple
+Archie
+Arnie
+Art
+Arthur
+Baldric
+Bartholomew
+Bill
+Blake
+Brayden
+Brendan
+Brock
+Bronte
+Brick
+Bruce
+Bryce
+Buck
+Burt
+Butch
+Byrne
+Byron
+Camryn
+Carl
+Carter
+Casimir
+Cassian
+Charles
+Charlton
+Chip
+Clark
+Claudius
+Clement
+Cleveland
+Cliff
+Clinton
+Cletus
+Collin
+Crush
+Cy
+Damian
+Danny
+Darcey
+Darell
+Darin
+Deangelo
+Denholm
+Desmond
+Devin
+Dirk
+Dominic
+Donny
+Driscoll
+Duke
+Duncan
+Edgar
+Eliot
+Eliott
+Elric
+Elwood
+Emmanuel
+Fenton
+Fitz
+Flick
+Flint
+Flip
+Francis
+Frank
+Frankie
+Fridge
+Fulton
+Gannon
+Garret
+Gary
+Goddard
+Godwin
+Goodwin
+Gordon
+Graeme
+Grandpa
+Gratian
+Grendel
+Han
+Harry
+Hartley
+Harvey
+Henderson
+Holden
+Homer
+Horatio
+Huffie
+Hungry
+Hugo
+Irvine
+Jacob
+Jake
+Jamar
+Jamie
+Jamison
+Janel
+Jaydon
+Jaye
+Jayne
+Jean-Luc
+Jeb
+Jed
+Jemmy
+Jermaine
+Jerrie
+Jim
+Joachim
+Joey
+Johnathan
+John
+Johnny
+Jonathon
+Josh
+Josiah
+Kennard
+Keziah
+Lando
+Lanny
+Launce
+Leland
+Lennox
+Lenny
+Leonard
+Leroy
+Lief
+Linden
+Linton
+Lorde
+Loreto
+Lou
+Lucas
+Luke
+Malachi
+Malcolm
+Manley
+Marion
+Max
+Maynard
+Melvyn
+Michael
+Mike
+Milton
+Montague
+Monte
+Monty
+Nat
+Nathaniel
+Nick
+Nikolas
+Noah
+Opie
+Osbert
+Osborn
+Osborne
+Osmund
+Oswald
+Paget
+Patrick
+Patton
+Percival
+Persh
+Rastus
+Raymond
+Rayner
+Reuben
+Reynard
+Richard
+Rodger
+Roger
+Romayne
+Roscoe
+Roswell
+Royce
+Rube
+Rusty
+Sal
+Sawyer
+Scotty
+Seymour
+Shane
+Shiloh
+Smoke
+Simon
+Sloan
+Sorrel
+Spike
+Sybil
+Syd
+Tamsin
+Taylor
+Tel
+Terrell
+Tim
+Timothy
+Todd
+Trip
+Tye
+Uland
+Ulric
+Vaughn
+Vince
+Vinny
+Walter
+Ward
+Warner
+Wayne
+Whitaker
+William
+Willy
+Woodrow
+Zack
+Zane
+Zeke
+Jacob
+Michael
+Ethan
+Joshua
+Daniel
+Alexander
+Anthony
+William
+Christopher
+Matthew
+Jayden
+Andrew
+Joseph
+David
+Noah
+Aiden
+James
+Ryan
+Logan
+John
+Nathan
+Elijah
+Christian
+Gabriel
+Benjamin
+Jonathan
+Tyler
+Samuel
+Nicholas
+Gavin
+Dylan
+Jackson
+Brandon
+Caleb
+Mason
+Angel
+Isaac
+Evan
+Jack
+Kevin
+Jose
+Isaiah
+Luke
+Landon
+Justin
+Lucas
+Zachary
+Jordan
+Robert
+Aaron
+Brayden
+Thomas
+Cameron
+Hunter
+Austin
+Adrian
+Connor
+Owen
+Aidan
+Jason
+Julian
+Wyatt
+Charles
+Luis
+Carter
+Juan
+Chase
+Diego
+Jeremiah
+Brody
+Xavier
+Adam
+Carlos
+Sebastian
+Liam
+Hayden
+Nathaniel
+Henry
+Jesus
+Ian
+Tristan
+Bryan
+Sean
+Cole
+Alex
+Eric
+Brian
+Jaden
+Carson
+Blake
+Ayden
+Cooper
+Dominic
+Brady
+Caden
+Josiah
+Kyle
+Colton
+Kaden
+Eli
+Miguel
+Antonio
+Parker
+Steven
+Alejandro
+Riley
+Richard
+Timothy
+Devin
+Jesse
+Victor
+Jake
+Joel
+Colin
+Kaleb
+Bryce
+Levi
+Oliver
+Oscar
+Vincent
+Ashton
+Cody
+Micah
+Preston
+Marcus
+Max
+Patrick
+Seth
+Jeremy
+Peyton
+Nolan
+Ivan
+Damian
+Maxwell
+Alan
+Kenneth
+Jonah
+Jorge
+Mark
+Giovanni
+Eduardo
+Grant
+Collin
+Gage
+Omar
+Emmanuel
+Trevor
+Edward
+Ricardo
+Cristian
+Nicolas
+Kayden
+George
+Jaxon
+Paul
+Braden
+Elias
+Andres
+Derek
+Garrett
+Tanner
+Malachi
+Conner
+Fernando
+Cesar
+Javier
+Miles
+Jaiden
+Alexis
+Leonardo
+Santiago
+Francisco
+Cayden
+Shane
+Edwin
+Hudson
+Travis
+Bryson
+Erick
+Jace
+Hector
+Josue
+Peter
+Jaylen
+Mario
+Manuel
+Abraham
+Grayson
+Damien
+Kaiden
+Spencer
+Stephen
+Edgar
+Wesley
+Shawn
+Trenton
+Jared
+Jeffrey
+Landen
+Johnathan
+Bradley
+Braxton
+Ryder
+Camden
+Roman
+Asher
+Brendan
+Maddox
+Sergio
+Israel
+Andy
+Lincoln
+Erik
+Donovan
+Raymond
+Avery
+Rylan
+Dalton
+Harrison
+Andre
+Martin
+Keegan
+Marco
+Jude
+Sawyer
+Dakota
+Leo
+Calvin
+Kai
+Drake
+Troy
+Zion
+Clayton
+Roberto
+Zane
+Gregory
+Tucker
+Rafael
+Kingston
+Dominick
+Ezekiel
+Griffin
+Devon
+Drew
+Lukas
+Johnny
+Ty
+Pedro
+Tyson
+Caiden
+Mateo
+Braylon
+Cash
+Aden
+Chance
+Taylor
+Marcos
+Maximus
+Ruben
+Emanuel
+Simon
+Corbin
+Brennan
+Dillon
+Skyler
+Myles
+Xander
+Jaxson
+Dawson
+Kameron
+Kyler
+Axel
+Colby
+Jonas
+Joaquin
+Payton
+Brock
+Frank
+Enrique
+Quinn
+Emilio
+Malik
+Grady
+Angelo
+Julio
+Derrick
+Raul
+Fabian
+Corey
+Gerardo
+Dante
+Ezra
+Armando
+Allen
+Theodore
+Gael
+Amir
+Zander
+Adan
+Maximilian
+Randy
+Easton
+Dustin
+Luca
+Phillip
+Julius
+Charlie
+Ronald
+Jakob
+Cade
+Brett
+Trent
+Silas
+Keith
+Emiliano
+Trey
+Jalen
+Darius
+Lane
+Jerry
+Jaime
+Scott
+Graham
+Weston
+Braydon
+Anderson
+Rodrigo
+Pablo
+Saul
+Danny
+Donald
+Elliot
+Brayan
+Dallas
+Lorenzo
+Casey
+Mitchell
+Alberto
+Tristen
+Rowan
+Jayson
+Gustavo
+Aaden
+Amari
+Dean
+Braeden
+Declan
+Chris
+Ismael
+Dane
+Louis
+Arturo
+Brenden
+Felix
+Jimmy
+Cohen
+Tony
+Holden
+Reid
+Abel
+Bennett
+Zackary
+Arthur
+Nehemiah
+Ricky
+Esteban
+Cruz
+Finn
+Mauricio
+Dennis
+Keaton
+Albert
+Marvin
+Mathew
+Larry
+Moises
+Issac
+Philip
+Quentin
+Curtis
+Greyson
+Jameson
+Everett
+Jayce
+Darren
+Elliott
+Uriel
+Alfredo
+Hugo
+Alec
+Jamari
+Marshall
+Walter
+Judah
+Jay
+Lance
+Beau
+Ali
+Landyn
+Yahir
+Phoenix
+Nickolas
+Kobe
+Bryant
+Maurice
+Russell
+Leland
+Colten
+Reed
+Davis
+Joe
+Ernesto
+Desmond
+Kade
+Reece
+Morgan
+Ramon
+Rocco
+Orlando
+Ryker
+Brodie
+Paxton
+Jacoby
+Douglas
+Kristopher
+Gary
+Lawrence
+Izaiah
+Solomon
+Nikolas
+Mekhi
+Justice
+Tate
+Jaydon
+Salvador
+Shaun
+Alvin
+Eddie
+Kane
+Davion
+Zachariah
+Damien
+Titus
+Kellen
+Camron
+Isiah
+Javon
+Nasir
+Milo
+Johan
+Byron
+Jasper
+Jonathon
+Chad
+Marc
+Kelvin
+Chandler
+Sam
+Cory
+Deandre
+River
+Reese
+Roger
+Quinton
+Talon
+Romeo
+Franklin
+Noel
+Alijah
+Guillermo
+Gunner
+Damon
+Jadon
+Emerson
+Micheal
+Bruce
+Terry
+Kolton
+Melvin
+Beckett
+Porter
+August
+Brycen
+Dayton
+Jamarion
+Leonel
+Karson
+Zayden
+Keagan
+Carl
+Khalil
+Cristopher
+Nelson
+Braiden
+Moses
+Isaias
+Roy
+Triston
+Walker
+Kale
\ No newline at end of file
diff --git a/config/names/last.txt b/config/names/last.txt
new file mode 100644
index 00000000..9bdb16eb
--- /dev/null
+++ b/config/names/last.txt
@@ -0,0 +1,620 @@
+Whittier
+Dimeling
+Blaine
+Dennis
+Adams
+Rader
+Murray
+Millhouse
+Ludwig
+Burris
+Shupe
+Mary
+Zadovsky
+Philips
+Wise
+Gronko
+Jardine
+Black
+Mitchell
+Enderly
+Stall
+Harrow
+Atweeke
+Sealis
+Conrad
+Lucy
+Stewart
+Green
+Feufer
+Warren
+Campbell
+Shafer
+Woodworth
+Magor
+Logue
+Reichard
+Day
+Dugmore
+Murray
+Greenawalt
+Jyllian
+Osterwise
+Styles
+Cavalet
+Garneys
+Raub
+Sholl
+Chauvin
+Poley
+Todd
+Brandenburg
+Baer
+Pritchard
+Pinney
+Kadel
+Anderson
+Clarke
+Hunt
+Gadow
+Stough
+Marcotte
+Brooks
+Watson
+Nash
+Sheets
+Ashbaugh
+Zimmer
+Noton
+Dean
+Fleming
+Draudy
+Bluetenberger
+Fischer
+Hawkins
+Poehl
+Addison
+Mcintosh
+Keppel
+Kimple
+Alice
+Stone
+Fiscina
+Leichter
+Wile
+Callison
+Cowper
+Harrold
+Carr
+Eckhardstein
+Wilkerson
+Shirey
+Benford
+Reade
+Baskett
+Seidner
+Gettemy
+Joyce
+Judge
+Burkett
+Kiefer
+Carmichael
+Hirleman
+Wells
+Isemann
+Cressman
+Highlands
+Briggs
+Rowley
+Coldsmith
+Berkheimer
+Hill
+Maclagan
+Mcfall
+Mens
+Braun
+James
+Sloan
+Bould
+Overstreet
+Kanaga
+Polson
+Finlay
+Sandys
+Bousum
+Howard
+Treeby
+Stainforth
+Werner
+Sulyard
+Marriman
+Weinstein
+Butterfill
+Mason
+Coates
+Peters
+Gregory
+Wilo
+Edwards
+Barnes
+Harding
+Tireman
+Lombardi
+Roberts
+Faqua
+Basmanoff
+Mccune
+Mckendrick
+Oppenheimer
+Oneal
+Focell
+Tedrow
+Fields
+Ryals
+Best
+Zaun
+Knapp
+Linton
+Jackson
+Bullard
+Mcloskey
+Zoucks
+Heckendora
+Hoenshell
+Woollard
+Mueller
+Burns
+Franks
+Goodman
+Stern
+Robinson
+Hooker
+David
+Fitzgerald
+Vanleer
+Beach
+Flickinger
+Metzer
+Bynum
+Stafford
+Osteen
+Johnson
+Paynter
+Thomlinson
+Simmons
+Basinger
+Fisher
+Bunten
+Compton
+Archibald
+Catherina
+Rahl
+Bowchiew
+Tennant
+Mccullough
+Margaret
+Schaeffer
+Sommer
+Beail
+Merryman
+Knapenberger
+Patterson
+Houston
+Lacon
+Levett
+Sullivan
+Sidower
+Laborde
+Stroh
+Hice
+Biery
+Christman
+Staymates
+Sauter
+Snyder
+Bratton
+Sybilla
+Altmann
+Mathews
+Newbern
+Baker
+Kemble
+Mingle
+Unk
+Otis
+Quinn
+Bell
+Roberts
+Wood
+Ullman
+Bicknell
+Gibson
+Rohtin
+James
+Wallick
+Eggbert
+Losey
+Neely
+Catleay
+McDonald
+Beedell
+Williamson
+Bennett
+Potter
+Caldwell
+Lowe
+Durstine
+King
+Gardner
+Ulery
+Rifler
+Trovato
+Thomas
+Nehling
+Baum
+Werry
+Mcmullen
+Koster
+Willey
+Mildred
+Straub
+Haynes
+Baxter
+Ackerley
+Greene
+Atkinson
+Davis
+Weeter
+Milne
+Leech
+Clewett
+Ewing
+Hook
+Reighner
+Welty
+Jenkins
+Bennett
+Swarner
+Hawker
+Agg
+Batten
+Cherry
+Shaffer
+Yeskey
+Stephenson
+Pycroft
+Larson
+Joghs
+Keener
+Christopher
+Roadman
+Echard
+Priebe
+Auman
+Kemerer
+Sutton
+Prechtl
+Cowart
+Ringer
+Garratt
+Siegrist
+Seelig
+Lafortune
+Dryfus
+Isaman
+Teagarden
+Evans
+Wolfe
+Fiddler
+Jowers
+Aultman
+Olphert
+Howe
+Leslie
+Stocker
+Bode
+Whirlow
+Ann
+Mcclymonds
+Guess
+Taggart
+Pratt
+Moon
+Huey
+Hegarty
+Meyers
+Stahl
+Nickolson
+Mortland
+Perkins
+Thorley
+Fuchs
+Ray
+Schrader
+Kellogg
+Woodward
+Faust
+Roby
+Bashline
+Cypret
+Laurenzi
+Minnie
+Houser
+Langston
+Anderson
+Barrett
+Wible
+Hujsak
+Wardle
+Pershing
+Kuster
+Driggers
+Wheeler
+Garland
+Alliman
+Hoover
+Camp
+Hall
+Parkinson
+Swabey
+Mull
+Cox
+Hanford
+Stange
+Wolff
+Jesse
+Brinigh
+Koepple
+Schmidt
+Muller
+Schofield
+Zalack
+Pfeifer
+Fea
+Blackburn
+Ward
+Kifer
+Costello
+Donkin
+Osterweis
+Brindle
+Llora
+Duncan
+Ehret
+Fryer
+Summy
+Richards
+Boyer
+Hutton
+Mosser
+Lester
+Stroble
+Randolph
+Lord
+Scott
+Nicholas
+Smail
+Ratcliff
+Riggle
+Newton
+Sanders
+Mitchell
+Lauffer
+Aggley
+Moberly
+Hynes
+Pennington
+Woolery
+Hardie
+Blessig
+Tanner
+Demuth
+Fraser
+Henry
+Shick
+Ironmonger
+Scherer
+Field
+Prevatt
+Earl
+Paulson
+Curry
+Powers
+Rosensteel
+Hoopengarner
+Weisgarber
+Elliott
+Pearsall
+Lowstetter
+Holdeman
+Kepplinger
+Bloise
+Hunter
+Glover
+Hayhurst
+Wentzel
+Owens
+Smith
+Steele
+Leach
+Armstrong
+Wheeler
+Easter
+Greenwood
+Woodward
+Pratt
+Buzzard
+Cox
+Eliza
+Rockwell
+Zeal
+Harshman
+Northey
+Tilton
+Richter
+Moore
+Jerome
+Hardy
+Bickerson
+White
+Beck
+Waldron
+Fulton
+Miller
+Sandford
+Hughes
+Winton
+Digson
+Byers
+Hincken
+Shaner
+Todd
+Whiteman
+Lineman
+Albright
+Hastings
+Elderson
+Garrison
+Rose
+Kelley
+Quirin
+Dickinson
+Young
+Ramos
+Jewell
+Endsley
+Briner
+Jenner
+Saylor
+Bash
+Blyant
+Rhinehart
+Prescott
+Kirkson
+Picard
+Riker
+Spock
+Skywalker
+Vader
+Muggins
+Buttersworth
+Stamos
+Sagan
+Hawking
+Dawkins
+Goebbles
+McShain
+McDonohugh
+Power
+Smith
+Jones
+Williams
+Brown
+Taylor
+Davies
+Wilson
+Evans
+Thomas
+Roberts
+Johnson
+Walker
+Wright
+Robinson
+Thompson
+Hughes
+White
+Edwards
+Hall
+Patel
+Green
+Martins
+Lewis
+Wood
+Jackson
+Clarke
+Harris
+Clark
+Scott
+Turner
+Hill
+Moore
+Cooper
+Morris
+Ward
+Watson
+Morgan
+Anderson
+Harrison
+King
+Campbell
+Young
+Mitchell
+Baker
+James
+Kelly
+Allen
+Bell
+Phillips
+Lee
+Stewart
+Miller
+Parker
+Simpson
+Bennett
+Davis
+Griffiths
+Shaw
+Price
+Cook
+Richardson
+Murray
+Marshall
+Begum
+Murphy
+Khan
+Gray
+Collins
+Bailey
+Carter
+Robertson
+Graham
+Adams
+Richards
+Cox
+Singh
+Hussain
+Ellis
+Wilkinson
+Foster
+Thomson
+Russell
+Ali
+Reid
+Rathens
+Rathen
+Mason
+Chapman
+Powell
+Owen
+Ahmed
+Gibson
+Rogers
+Webb
+Holmes
+Mills
+Matthews
+Hunt
+Palmer
+Lloyd
+Kaur
+Fisher
+Ivanov
+Smirnov
+Vasilyev
+Petrov
+Kuznetsov
+Mikhaylov
+Pavlov
+Semenov
+Andreev
+Alekseev
\ No newline at end of file
diff --git a/config/names/ninjaname.txt b/config/names/ninjaname.txt
new file mode 100644
index 00000000..828bc170
--- /dev/null
+++ b/config/names/ninjaname.txt
@@ -0,0 +1,45 @@
+Shadow
+Sarutobi
+Smoke
+Rain
+Scorpion
+Zero
+Ermac
+Saibot
+Sun
+Moon
+Cyrax
+Raphael
+Michaelangelo
+Cloud
+Donatello
+Leonardo
+Splinter
+Shredder
+Hazuki
+Hien
+Hiryu
+Ryu
+Hayabusa
+Midnight
+Seven
+Hanzo
+Blood
+Iga
+Koga
+Hero
+Hiro
+Phantom
+Baki
+Ogre
+Daemon
+Goemon
+Throat
+Death
+Aria
+Bro
+Fox
+Null
+Raiden
+Samurai
+Eater
\ No newline at end of file
diff --git a/config/names/ninjatitle.txt b/config/names/ninjatitle.txt
new file mode 100644
index 00000000..429425ca
--- /dev/null
+++ b/config/names/ninjatitle.txt
@@ -0,0 +1,46 @@
+Master
+Sensei
+Swift
+Merciless
+Assassin
+Rogue
+Hunter
+Widower
+Orphaner
+Stalker
+Killer
+Silent
+Silencing
+Quick
+Agile
+Merciful
+Ninja
+Shinobi
+Initiate
+Grandmaster
+Strider
+Striker
+Slayer
+Awesome
+Ender
+Dr.
+Night
+Crimson
+Grappler
+Ulimate
+Remorseless
+Deep
+Dragon
+Cruel
+Nightshade
+Black
+Gray
+Solid
+Liquid
+Solidus
+Steel
+Nickel
+Silver
+Singing
+Snake
+Acid
\ No newline at end of file
diff --git a/config/names/verbs.txt b/config/names/verbs.txt
new file mode 100644
index 00000000..5bb8d6e3
--- /dev/null
+++ b/config/names/verbs.txt
@@ -0,0 +1,633 @@
+accept
+add
+admire
+admit
+advise
+afford
+agree
+alert
+allow
+amuse
+analyse
+announce
+annoy
+answer
+apologise
+appear
+applaud
+appreciate
+approve
+argue
+arrange
+arrest
+arrive
+ask
+attach
+attack
+attempt
+attend
+attract
+avoid
+back
+bake
+balance
+ban
+bang
+bare
+bat
+bathe
+battle
+beam
+beg
+behave
+belong
+bleach
+bless
+blind
+blink
+blot
+blush
+boast
+boil
+bolt
+bomb
+book
+bore
+borrow
+bounce
+bow
+box
+brake
+brake
+branch
+breathe
+bruise
+brush
+bubble
+bump
+burn
+bury
+buzz
+calculate
+call
+camp
+care
+carry
+carve
+cause
+challenge
+change
+charge
+chase
+cheat
+check
+cheer
+chew
+choke
+chop
+claim
+clap
+clean
+clear
+clip
+close
+coach
+coil
+collect
+colour
+comb
+command
+communicate
+compare
+compete
+complain
+complete
+concentrate
+concern
+confess
+confuse
+connect
+consider
+consist
+contain
+continue
+copy
+correct
+cough
+count
+cover
+crack
+crash
+crawl
+cross
+crush
+cry
+cure
+curl
+curve
+cycle
+dam
+damage
+dance
+dare
+decay
+deceive
+decide
+decorate
+delay
+delight
+deliver
+depend
+describe
+desert
+deserve
+destroy
+detect
+develop
+disagree
+disappear
+disapprove
+disarm
+discover
+dislike
+divide
+double
+doubt
+drag
+drain
+dream
+dress
+drip
+drop
+drown
+drum
+dry
+dust
+earn
+educate
+embarrass
+employ
+empty
+encourage
+end
+enjoy
+enter
+entertain
+escape
+examine
+excite
+excuse
+exercise
+exist
+expand
+expect
+explain
+explode
+extend
+face
+fade
+fail
+fancy
+fasten
+fax
+fear
+fence
+fetch
+file
+fill
+film
+fire
+fit
+fix
+flap
+flash
+float
+flood
+flow
+flower
+fold
+follow
+fool
+force
+form
+found
+frame
+frighten
+fry
+gather
+gaze
+glow
+glue
+grab
+grate
+grease
+greet
+grin
+grip
+groan
+guarantee
+guard
+guess
+guide
+hammer
+hand
+handle
+hang
+happen
+harass
+harm
+hate
+haunt
+head
+heal
+heap
+heat
+help
+hook
+hop
+hope
+hover
+hug
+hum
+hunt
+hurry
+identify
+ignore
+imagine
+impress
+improve
+include
+increase
+influence
+inform
+inject
+injure
+instruct
+intend
+interest
+interfere
+interrupt
+introduce
+invent
+invite
+irritate
+itch
+jail
+jam
+jog
+join
+joke
+judge
+juggle
+jump
+kick
+kill
+kiss
+kneel
+knit
+knock
+knot
+label
+land
+last
+laugh
+launch
+learn
+level
+license
+lick
+lie
+lighten
+like
+list
+listen
+live
+load
+lock
+long
+look
+love
+man
+manage
+march
+mark
+marry
+match
+mate
+matter
+measure
+meddle
+melt
+memorise
+mend
+messup
+milk
+mine
+miss
+mix
+moan
+moor
+mourn
+move
+muddle
+mug
+multiply
+murder
+nail
+name
+need
+nest
+nod
+note
+notice
+number
+obey
+object
+observe
+obtain
+occur
+offend
+offer
+open
+order
+overflow
+owe
+own
+pack
+paddle
+paint
+park
+part
+pass
+paste
+pat
+pause
+peck
+pedal
+peel
+peep
+perform
+permit
+phone
+pick
+pinch
+pine
+place
+plan
+plant
+play
+please
+plug
+point
+poke
+polish
+pop
+possess
+post
+pour
+practise
+pray
+preach
+precede
+prefer
+prepare
+present
+preserve
+press
+pretend
+prevent
+prick
+print
+produce
+program
+promise
+protect
+provide
+pull
+pump
+punch
+puncture
+punish
+push
+question
+queue
+race
+radiate
+rain
+raise
+reach
+realise
+receive
+recognise
+record
+reduce
+reflect
+refuse
+regret
+reign
+reject
+rejoice
+relax
+release
+rely
+remain
+remember
+remind
+remove
+repair
+repeat
+replace
+reply
+report
+reproduce
+request
+rescue
+retire
+return
+rhyme
+rinse
+risk
+rob
+rock
+roll
+rot
+rub
+ruin
+rule
+rush
+sack
+sail
+satisfy
+save
+saw
+scare
+scatter
+scold
+scorch
+scrape
+scratch
+scream
+screw
+scribble
+scrub
+seal
+search
+separate
+serve
+settle
+shade
+share
+shave
+shelter
+shiver
+shock
+shop
+shrug
+sigh
+sign
+signal
+sin
+sip
+ski
+skip
+slap
+slip
+slow
+smash
+smell
+smile
+smoke
+snatch
+sneeze
+sniff
+snore
+snow
+soak
+soothe
+sound
+spare
+spark
+sparkle
+spell
+spill
+spoil
+spot
+spray
+sprout
+squash
+squeak
+squeal
+squeeze
+stain
+stamp
+stare
+start
+stay
+steer
+step
+stir
+stitch
+stop
+store
+strap
+strengthen
+stretch
+strip
+stroke
+stuff
+subtract
+succeed
+suck
+suffer
+suggest
+suit
+supply
+support
+suppose
+surprise
+surround
+suspect
+suspend
+switch
+talk
+tame
+tap
+taste
+tease
+telephone
+tempt
+terrify
+test
+thank
+thaw
+tick
+tickle
+tie
+time
+tip
+tire
+touch
+tour
+tow
+trace
+trade
+train
+transport
+trap
+travel
+treat
+tremble
+trick
+trip
+trot
+trouble
+trust
+try
+tug
+tumble
+turn
+twist
+type
+undress
+unfasten
+unite
+unlock
+unpack
+untidy
+use
+vanish
+visit
+wail
+wait
+walk
+wander
+want
+warm
+warn
+wash
+waste
+watch
+water
+wave
+weigh
+welcome
+whine
+whip
+whirl
+whisper
+whistle
+wink
+wipe
+wish
+wobble
+wonder
+work
+worry
+wrap
+wreck
+wrestle
+wriggle
+yawn
+yell
+zip
+zoom
\ No newline at end of file
diff --git a/config/names/wizardfirst.txt b/config/names/wizardfirst.txt
new file mode 100644
index 00000000..18806ca7
--- /dev/null
+++ b/config/names/wizardfirst.txt
@@ -0,0 +1,36 @@
+Jim
+Gulstaff
+Gandalf
+Grimm
+Mordenkainen
+Elminister
+Saruman
+Vaarsuvius
+Yoda
+Zul
+Nihilus
+Vecna
+Mogan
+Circe
+Prospero
+Raistlin
+Rasputin
+Tzeentch
+Khelben
+Dumbledor
+Houdini
+Terefi
+Urza
+Tenser
+Zagyg
+Mystryl
+Boccob
+Merlin
+Archchancellor
+Radagast
+Kreol
+Kaschei
+Lina
+Morgan
+Alatar
+Palando
\ No newline at end of file
diff --git a/config/names/wizardsecond.txt b/config/names/wizardsecond.txt
new file mode 100644
index 00000000..1fe60bdb
--- /dev/null
+++ b/config/names/wizardsecond.txt
@@ -0,0 +1,39 @@
+the Powerful
+the Great
+the Magician
+the Wise
+the Seething
+the Amazing
+the Spiral King
+Darkmagic
+the White
+the Gray
+Shado
+the Sorcelator
+the Raven
+the Emperor
+the Brown
+Weatherwax
+the Destroyer
+the Deathless
+Yagg
+the Remorseful
+the Weeping
+the Unending
+the All Knowing
+Dark
+Smith
+the Conquerer
+the Unstoppable
+Gray
+of Void
+Unseen
+Darko
+Honko
+the Bandit Killer
+the Dragon Spooker
+Inverse
+le Fay
+the Blue
+the Red
+the Benevolent
\ No newline at end of file
diff --git a/data/investigate/gravity.html b/data/investigate/gravity.html
index b372edd0..136ba45c 100644
--- a/data/investigate/gravity.html
+++ b/data/investigate/gravity.html
@@ -1,2 +1,2 @@
-21:23 [0x2009fc1] (92,152,1) || the gravitational generator has regained power.
-21:23 [0x2009fc1] (92,152,1) || the gravitational generator is now charging.
+22:07 [0x2009fed] (92,152,1) || the gravitational generator has regained power.
+22:07 [0x2009fed] (92,152,1) || the gravitational generator is now charging.
diff --git a/icons/atmos/connector.dmi b/icons/atmos/connector.dmi
new file mode 100644
index 00000000..cf4ea7a3
Binary files /dev/null and b/icons/atmos/connector.dmi differ
diff --git a/icons/atmos/digital_tvalve.dmi b/icons/atmos/digital_tvalve.dmi
new file mode 100644
index 00000000..b34d2903
Binary files /dev/null and b/icons/atmos/digital_tvalve.dmi differ
diff --git a/icons/atmos/digital_valve.dmi b/icons/atmos/digital_valve.dmi
new file mode 100644
index 00000000..b1adc8b6
Binary files /dev/null and b/icons/atmos/digital_valve.dmi differ
diff --git a/icons/atmos/filter.dmi b/icons/atmos/filter.dmi
new file mode 100644
index 00000000..8b5cd81c
Binary files /dev/null and b/icons/atmos/filter.dmi differ
diff --git a/icons/atmos/heat.dmi b/icons/atmos/heat.dmi
new file mode 100644
index 00000000..6d1ed47e
Binary files /dev/null and b/icons/atmos/heat.dmi differ
diff --git a/icons/atmos/injector.dmi b/icons/atmos/injector.dmi
new file mode 100644
index 00000000..03a60d2e
Binary files /dev/null and b/icons/atmos/injector.dmi differ
diff --git a/icons/atmos/junction.dmi b/icons/atmos/junction.dmi
new file mode 100644
index 00000000..70286bde
Binary files /dev/null and b/icons/atmos/junction.dmi differ
diff --git a/icons/atmos/manifold.dmi b/icons/atmos/manifold.dmi
new file mode 100644
index 00000000..f6b24341
Binary files /dev/null and b/icons/atmos/manifold.dmi differ
diff --git a/icons/atmos/mixer.dmi b/icons/atmos/mixer.dmi
new file mode 100644
index 00000000..0fcb9282
Binary files /dev/null and b/icons/atmos/mixer.dmi differ
diff --git a/icons/atmos/omni_devices.dmi b/icons/atmos/omni_devices.dmi
new file mode 100644
index 00000000..494ea7e2
Binary files /dev/null and b/icons/atmos/omni_devices.dmi differ
diff --git a/icons/atmos/passive_gate.dmi b/icons/atmos/passive_gate.dmi
new file mode 100644
index 00000000..df8c3897
Binary files /dev/null and b/icons/atmos/passive_gate.dmi differ
diff --git a/icons/atmos/pipe_underlays.dmi b/icons/atmos/pipe_underlays.dmi
new file mode 100644
index 00000000..0edf79e5
Binary files /dev/null and b/icons/atmos/pipe_underlays.dmi differ
diff --git a/icons/atmos/pipes.dmi b/icons/atmos/pipes.dmi
new file mode 100644
index 00000000..019be28f
Binary files /dev/null and b/icons/atmos/pipes.dmi differ
diff --git a/icons/atmos/pump.dmi b/icons/atmos/pump.dmi
new file mode 100644
index 00000000..a1e2d43c
Binary files /dev/null and b/icons/atmos/pump.dmi differ
diff --git a/icons/atmos/tank.dmi b/icons/atmos/tank.dmi
new file mode 100644
index 00000000..322f43e8
Binary files /dev/null and b/icons/atmos/tank.dmi differ
diff --git a/icons/atmos/tvalve.dmi b/icons/atmos/tvalve.dmi
new file mode 100644
index 00000000..197862b3
Binary files /dev/null and b/icons/atmos/tvalve.dmi differ
diff --git a/icons/atmos/valve.dmi b/icons/atmos/valve.dmi
new file mode 100644
index 00000000..04351917
Binary files /dev/null and b/icons/atmos/valve.dmi differ
diff --git a/icons/atmos/vent_pump.dmi b/icons/atmos/vent_pump.dmi
new file mode 100644
index 00000000..88461036
Binary files /dev/null and b/icons/atmos/vent_pump.dmi differ
diff --git a/icons/atmos/vent_scrubber.dmi b/icons/atmos/vent_scrubber.dmi
new file mode 100644
index 00000000..e8e7f916
Binary files /dev/null and b/icons/atmos/vent_scrubber.dmi differ
diff --git a/icons/atmos/volume_pump.dmi b/icons/atmos/volume_pump.dmi
new file mode 100644
index 00000000..9114b6ac
Binary files /dev/null and b/icons/atmos/volume_pump.dmi differ
diff --git a/icons/effects/ss13_dark_alpha6.dmi b/icons/effects/ss13_dark_alpha6.dmi
new file mode 100644
index 00000000..74233735
Binary files /dev/null and b/icons/effects/ss13_dark_alpha6.dmi differ
diff --git a/icons/mob/pai.dmi b/icons/mob/pai.dmi
new file mode 100644
index 00000000..f286389b
Binary files /dev/null and b/icons/mob/pai.dmi differ
diff --git a/icons/obj/pipeturbine.dmi b/icons/obj/pipeturbine.dmi
new file mode 100644
index 00000000..fccec4c9
Binary files /dev/null and b/icons/obj/pipeturbine.dmi differ
|