"
+
+ if(!gelled)
+ . += "Alternative Treatment: Apply bone gel directly to injured limb, then apply surgical tape to begin bone regeneration. This is both excruciatingly painful and slow, and only recommended in dire circumstances.\n"
+ else if(!taped)
+ . += "Continue Alternative Treatment: Apply surgical tape directly to injured limb to begin bone regeneration. Note, this is both excruciatingly painful and slow.\n"
+ else
+ . += "Note: Bone regeneration in effect. Bone is [round(regen_points_current*100/regen_points_needed)]% regenerated.\n"
+
+ if(limb.body_zone == BODY_ZONE_HEAD)
+ . += "Cranial Trauma Detected: Patient will suffer random bouts of [severity == WOUND_SEVERITY_SEVERE ? "mild" : "severe"] brain traumas until bone is repaired."
+ else if(limb.body_zone == BODY_ZONE_CHEST && victim.blood_volume)
+ . += "Ribcage Trauma Detected: Further trauma to chest is likely to worsen internal bleeding until bone is repaired."
+ . += "
"
diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm
new file mode 100644
index 0000000000..f3e22807cf
--- /dev/null
+++ b/code/datums/wounds/burns.dm
@@ -0,0 +1,296 @@
+
+
+// TODO: well, a lot really, but specifically I want to add potential fusing of clothing/equipment on the affected area, and limb infections, though those may go in body part code
+/datum/wound/burn
+ a_or_from = "from"
+ wound_type = WOUND_BURN
+ processes = TRUE
+ sound_effect = 'sound/effects/wounds/sizzle1.ogg'
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE)
+
+ treatable_by = list(/obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) // sterilizer and alcohol will require reagent treatments, coming soon
+
+ // Flesh damage vars
+ /// How much damage to our flesh we currently have. Once both this and infestation reach 0, the wound is considered healed
+ var/flesh_damage = 5
+ /// Our current counter for how much flesh regeneration we have stacked from regenerative mesh/synthflesh/whatever, decrements each tick and lowers flesh_damage
+ var/flesh_healing = 0
+
+ // Infestation vars (only for severe and critical)
+ /// How quickly infection breeds on this burn if we don't have disinfectant
+ var/infestation_rate = 0
+ /// Our current level of infection
+ var/infestation = 0
+ /// Our current level of sanitization/anti-infection, from disinfectants/alcohol/UV lights. While positive, totally pauses and slowly reverses infestation effects each tick
+ var/sanitization = 0
+
+ /// Once we reach infestation beyond WOUND_INFESTATION_SEPSIS, we get this many warnings before the limb is completely paralyzed (you'd have to ignore a really bad burn for a really long time for this to happen)
+ var/strikes_to_lose_limb = 3
+
+
+/datum/wound/burn/handle_process()
+ . = ..()
+ if(strikes_to_lose_limb == 0)
+ victim.adjustToxLoss(0.5)
+ if(prob(1))
+ victim.visible_message("The infection on the remnants of [victim]'s [limb.name] shift and bubble nauseatingly!", "You can feel the infection on the remnants of your [limb.name] coursing through your veins!")
+ return
+
+ if(victim.reagents)
+ if(victim.reagents.has_reagent(/datum/reagent/medicine/spaceacillin))
+ sanitization += 0.9
+ if(victim.reagents.has_reagent(/datum/reagent/space_cleaner/sterilizine/))
+ sanitization += 0.9
+ if(victim.reagents.has_reagent(/datum/reagent/medicine/mine_salve))
+ sanitization += 0.3
+ flesh_healing += 0.5
+
+ if(limb.current_gauze)
+ limb.seep_gauze(WOUND_BURN_SANITIZATION_RATE)
+
+ if(flesh_healing > 0)
+ var/bandage_factor = (limb.current_gauze ? limb.current_gauze.splint_factor : 1)
+ flesh_damage = max(0, flesh_damage - 1)
+ flesh_healing = max(0, flesh_healing - bandage_factor) // good bandages multiply the length of flesh healing
+
+ // here's the check to see if we're cleared up
+ if((flesh_damage <= 0) && (infestation <= 1))
+ to_chat(victim, "The burns on your [limb.name] have cleared up!")
+ qdel(src)
+ return
+
+ // sanitization is checked after the clearing check but before the rest, because we freeze the effects of infection while we have sanitization
+ if(sanitization > 0)
+ var/bandage_factor = (limb.current_gauze ? limb.current_gauze.splint_factor : 1)
+ infestation = max(0, infestation - WOUND_BURN_SANITIZATION_RATE)
+ sanitization = max(0, sanitization - (WOUND_BURN_SANITIZATION_RATE * bandage_factor))
+ return
+
+ infestation += infestation_rate
+
+ switch(infestation)
+ if(0 to WOUND_INFECTION_MODERATE)
+ if(WOUND_INFECTION_MODERATE to WOUND_INFECTION_SEVERE)
+ if(prob(30))
+ victim.adjustToxLoss(0.2)
+ if(prob(6))
+ to_chat(victim, "The blisters on your [limb.name] ooze a strange pus...")
+ if(WOUND_INFECTION_SEVERE to WOUND_INFECTION_CRITICAL)
+ if(!disabling && prob(2))
+ to_chat(victim, "Your [limb.name] completely locks up, as you struggle for control against the infection!")
+ disabling = TRUE
+ else if(disabling && prob(8))
+ to_chat(victim, "You regain sensation in your [limb.name], but it's still in terrible shape!")
+ disabling = FALSE
+ else if(prob(20))
+ victim.adjustToxLoss(0.5)
+ if(WOUND_INFECTION_CRITICAL to WOUND_INFECTION_SEPTIC)
+ if(!disabling && prob(3))
+ to_chat(victim, "You suddenly lose all sensation of the festering infection in your [limb.name]!")
+ disabling = TRUE
+ else if(disabling && prob(3))
+ to_chat(victim, "You can barely feel your [limb.name] again, and you have to strain to retain motor control!")
+ disabling = FALSE
+ else if(prob(1))
+ to_chat(victim, "You contemplate life without your [limb.name]...")
+ victim.adjustToxLoss(0.75)
+ else if(prob(4))
+ victim.adjustToxLoss(1)
+ if(WOUND_INFECTION_SEPTIC to INFINITY)
+ if(prob(infestation))
+ switch(strikes_to_lose_limb)
+ if(3 to INFINITY)
+ to_chat(victim, "The skin on your [limb.name] is literally dripping off, you feel awful!")
+ if(2)
+ to_chat(victim, "The infection in your [limb.name] is literally dripping off, you feel horrible!")
+ if(1)
+ to_chat(victim, "Infection has just about completely claimed your [limb.name]!")
+ if(0)
+ to_chat(victim, "The last of the nerve endings in your [limb.name] wither away, as the infection completely paralyzes your joint connector.")
+ threshold_penalty = 120 // piss easy to destroy
+ var/datum/brain_trauma/severe/paralysis/sepsis = new (limb.body_zone)
+ victim.gain_trauma(sepsis)
+ strikes_to_lose_limb--
+
+/datum/wound/burn/get_examine_description(mob/user)
+ if(strikes_to_lose_limb <= 0)
+ return "[victim.p_their(TRUE)] [limb.name] is completely dead and unrecognizable as organic."
+
+ var/list/condition = list("[victim.p_their(TRUE)] [limb.name] [examine_desc]")
+ if(limb.current_gauze)
+ var/bandage_condition
+ switch(limb.current_gauze.absorption_capacity)
+ if(0 to 1.25)
+ bandage_condition = "nearly ruined "
+ if(1.25 to 2.75)
+ bandage_condition = "badly worn "
+ if(2.75 to 4)
+ bandage_condition = "slightly pus-stained "
+ if(4 to INFINITY)
+ bandage_condition = "clean "
+
+ condition += " underneath a dressing of [bandage_condition] [limb.current_gauze.name]"
+ else
+ switch(infestation)
+ if(WOUND_INFECTION_MODERATE to WOUND_INFECTION_SEVERE)
+ condition += ", with small spots of discoloration along the nearby veins!"
+ if(WOUND_INFECTION_SEVERE to WOUND_INFECTION_CRITICAL)
+ condition += ", with dark clouds spreading outwards under the skin!"
+ if(WOUND_INFECTION_CRITICAL to WOUND_INFECTION_SEPTIC)
+ condition += ", with streaks of rotten infection pulsating outward!"
+ if(WOUND_INFECTION_SEPTIC to INFINITY)
+ return "[victim.p_their(TRUE)] [limb.name] is a mess of char and rot, skin literally dripping off the bone with infection!"
+ else
+ condition += "!"
+
+ return "[condition.Join()]"
+
+/datum/wound/burn/get_scanner_description(mob/user)
+ if(strikes_to_lose_limb == 0)
+ var/oopsie = "Type: [name]\nSeverity: [severity_text()]"
+ oopsie += "
Infection Level: The infection is total. The bodypart is lost. Amputate or augment limb immediately.
"
+ return oopsie
+
+ . = ..()
+ . += "
"
+
+ if(infestation <= sanitization && flesh_damage <= flesh_healing)
+ . += "No further treatment required: Burns will heal shortly."
+ else
+ switch(infestation)
+ if(WOUND_INFECTION_MODERATE to WOUND_INFECTION_SEVERE)
+ . += "Infection Level: Moderate\n"
+ if(WOUND_INFECTION_SEVERE to WOUND_INFECTION_CRITICAL)
+ . += "Infection Level: Severe\n"
+ if(WOUND_INFECTION_CRITICAL to WOUND_INFECTION_SEPTIC)
+ . += "Infection Level: CRITICAL\n"
+ if(WOUND_INFECTION_SEPTIC to INFINITY)
+ . += "Infection Level: LOSS IMMINENT\n"
+ if(infestation > sanitization)
+ . += "\tSurgical debridement, antiobiotics/sterilizers, or regenerative mesh will rid infection. Paramedic UV penlights are also effective.\n"
+
+ if(flesh_damage > 0)
+ . += "Flesh damage detected: Please apply ointment or regenerative mesh to allow recovery.\n"
+ . += "
"
+
+/*
+ new burn common procs
+*/
+
+/// if someone is using ointment on our burns
+/datum/wound/burn/proc/ointment(obj/item/stack/medical/ointment/I, mob/user)
+ user.visible_message("[user] begins applying [I] to [victim]'s [limb.name]...", "You begin applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.name]...")
+ if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), extra_checks = CALLBACK(src, .proc/still_exists)))
+ return
+
+ limb.heal_damage(I.heal_brute, I.heal_burn)
+ user.visible_message("[user] applies [I] to [victim].", "You apply [I] to [user == victim ? "your" : "[victim]'s"] [limb.name].")
+ I.use(1)
+ sanitization += I.sanitization
+ flesh_healing += I.flesh_regeneration
+
+ if((infestation <= 0 || sanitization >= infestation) && (flesh_damage <= 0 || flesh_healing > flesh_damage))
+ to_chat(user, "You've done all you can with [I], now you must wait for the flesh on [victim]'s [limb.name] to recover.")
+ else
+ try_treating(I, user)
+
+/// if someone is using mesh on our burns
+/datum/wound/burn/proc/mesh(obj/item/stack/medical/mesh/I, mob/user)
+ user.visible_message("[user] begins wrapping [victim]'s [limb.name] with [I]...", "You begin wrapping [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
+ if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
+ return
+
+ limb.heal_damage(I.heal_brute, I.heal_burn)
+ user.visible_message("[user] applies [I] to [victim].", "You apply [I] to [user == victim ? "your" : "[victim]'s"] [limb.name].")
+ I.use(1)
+ sanitization += I.sanitization
+ flesh_healing += I.flesh_regeneration
+
+ if(sanitization >= infestation && flesh_healing > flesh_damage)
+ to_chat(user, "You've done all you can with [I], now you must wait for the flesh on [victim]'s [limb.name] to recover.")
+ else
+ try_treating(I, user)
+
+/// Paramedic UV penlights
+/datum/wound/burn/proc/uv(obj/item/flashlight/pen/paramedic/I, mob/user)
+ if(!COOLDOWN_FINISHED(I, uv_cooldown))
+ to_chat(user, "[I] is still recharging!")
+ return
+ if(infestation <= 0 || infestation < sanitization)
+ to_chat(user, "There's no infection to treat on [victim]'s [limb.name]!")
+ return
+
+ user.visible_message("[user] flashes the burns on [victim]'s [limb] with [I].", "You flash the burns on [user == victim ? "your" : "[victim]'s"] [limb.name] with [I].", vision_distance=COMBAT_MESSAGE_RANGE)
+ sanitization += I.uv_power
+ COOLDOWN_START(I, uv_cooldown, I.uv_cooldown_length)
+
+/datum/wound/burn/treat(obj/item/I, mob/user)
+ if(istype(I, /obj/item/stack/medical/ointment))
+ ointment(I, user)
+ else if(istype(I, /obj/item/stack/medical/mesh))
+ mesh(I, user)
+ else if(istype(I, /obj/item/flashlight/pen/paramedic))
+ uv(I, user)
+
+// people complained about burns not healing on stasis beds, so in addition to checking if it's cured, they also get the special ability to very slowly heal on stasis beds if they have the healing effects stored
+/datum/wound/burn/on_stasis()
+ . = ..()
+ if(flesh_healing > 0)
+ flesh_damage = max(0, flesh_damage - 0.2)
+ if((flesh_damage <= 0) && (infestation <= 1))
+ to_chat(victim, "The burns on your [limb.name] have cleared up!")
+ qdel(src)
+ return
+ if(sanitization > 0)
+ infestation = max(0, infestation - WOUND_BURN_SANITIZATION_RATE * 0.2)
+
+/datum/wound/burn/on_synthflesh(amount)
+ flesh_healing += amount * 0.5 // 20u patch will heal 10 flesh standard
+
+// we don't even care about first degree burns, straight to second
+/datum/wound/burn/moderate
+ name = "Second Degree Burns"
+ desc = "Patient is suffering considerable burns with mild skin penetration, weakening limb integrity and increased burning sensations."
+ treat_text = "Recommended application of topical ointment or regenerative mesh to affected region."
+ examine_desc = "is badly burned and breaking out in blisters"
+ occur_text = "breaks out with violent red burns"
+ severity = WOUND_SEVERITY_MODERATE
+ damage_mulitplier_penalty = 1.05
+ threshold_minimum = 50
+ threshold_penalty = 30 // burns cause significant decrease in limb integrity compared to other wounds
+ status_effect_type = /datum/status_effect/wound/burn/moderate
+ flesh_damage = 5
+ scar_keyword = "burnmoderate"
+
+/datum/wound/burn/severe
+ name = "Third Degree Burns"
+ desc = "Patient is suffering extreme burns with full skin penetration, creating serious risk of infection and greatly reduced limb integrity."
+ treat_text = "Recommended immediate disinfection and excision of any infected skin, followed by bandaging and ointment."
+ examine_desc = "appears seriously charred, with aggressive red splotches"
+ occur_text = "chars rapidly, exposing ruined tissue and spreading angry red burns"
+ severity = WOUND_SEVERITY_SEVERE
+ damage_mulitplier_penalty = 1.1
+ threshold_minimum = 90
+ threshold_penalty = 40
+ status_effect_type = /datum/status_effect/wound/burn/severe
+ treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh)
+ infestation_rate = 0.05 // appx 13 minutes to reach sepsis without any treatment
+ flesh_damage = 12.5
+ scar_keyword = "burnsevere"
+
+/datum/wound/burn/critical
+ name = "Catastrophic Burns"
+ desc = "Patient is suffering near complete loss of tissue and significantly charred muscle and bone, creating life-threatening risk of infection and negligible limb integrity."
+ treat_text = "Immediate surgical debriding of any infected skin, followed by potent tissue regeneration formula and bandaging."
+ examine_desc = "is a ruined mess of blanched bone, melted fat, and charred tissue"
+ occur_text = "vaporizes as flesh, bone, and fat melt together in a horrifying mess"
+ severity = WOUND_SEVERITY_CRITICAL
+ damage_mulitplier_penalty = 1.15
+ sound_effect = 'sound/effects/wounds/sizzle2.ogg'
+ threshold_minimum = 150
+ threshold_penalty = 80
+ status_effect_type = /datum/status_effect/wound/burn/critical
+ treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh)
+ infestation_rate = 0.15 // appx 4.33 minutes to reach sepsis without any treatment
+ flesh_damage = 20
+ scar_keyword = "burncritical"
diff --git a/code/datums/wounds/loss.dm b/code/datums/wounds/loss.dm
new file mode 100644
index 0000000000..6c5e536fb2
--- /dev/null
+++ b/code/datums/wounds/loss.dm
@@ -0,0 +1,41 @@
+/datum/wound/loss
+ name = "Dismembered"
+ desc = "oof ouch!!"
+
+ sound_effect = 'sound/effects/dismember.ogg'
+ severity = WOUND_SEVERITY_LOSS
+ threshold_minimum = 180
+ status_effect_type = null
+ scar_keyword = "dismember"
+ wound_flags = null
+
+/// Our special proc for our special dismembering, the wounding type only matters for what text we have
+/datum/wound/loss/proc/apply_dismember(obj/item/bodypart/dismembered_part, wounding_type=WOUND_SLASH)
+ if(!istype(dismembered_part) || !dismembered_part.owner || !(dismembered_part.body_zone in viable_zones) || isalien(dismembered_part.owner) || !dismembered_part.can_dismember())
+ qdel(src)
+ return
+
+ already_scarred = TRUE // so we don't scar a limb we don't have. If I add different levels of amputation desc, do it here
+
+ switch(wounding_type)
+ if(WOUND_BLUNT)
+ occur_text = "is shattered through the last bone holding it together, severing it completely!"
+ if(WOUND_SLASH)
+ occur_text = "is slashed through the last tissue holding it together, severing it completely!"
+ if(WOUND_PIERCE)
+ occur_text = "is pierced through the last tissue holding it together, severing it completely!"
+ if(WOUND_BURN)
+ occur_text = "is completely incinerated, falling to dust!"
+
+ victim = dismembered_part.owner
+
+ var/msg = "[victim]'s [dismembered_part.name] [occur_text]!"
+
+ victim.visible_message(msg, "Your [dismembered_part.name] [occur_text]!")
+
+ limb = dismembered_part
+ severity = WOUND_SEVERITY_LOSS
+ second_wind()
+ log_wound(victim, src)
+ dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE)
+ qdel(src)
diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm
new file mode 100644
index 0000000000..56c60b31d7
--- /dev/null
+++ b/code/datums/wounds/pierce.dm
@@ -0,0 +1,170 @@
+/*
+ Pierce
+*/
+
+/datum/wound/pierce
+ sound_effect = 'sound/weapons/slice.ogg'
+ processes = TRUE
+ wound_type = WOUND_PIERCE
+ treatable_by = list(/obj/item/stack/medical/suture)
+ treatable_tool = TOOL_CAUTERY
+ base_treat_time = 3 SECONDS
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE)
+
+ /// How much blood we start losing when this wound is first applied
+ var/initial_flow
+ /// If gauzed, what percent of the internal bleeding actually clots of the total absorption rate
+ var/gauzed_clot_rate
+
+ /// When hit on this bodypart, we have this chance of losing some blood + the incoming damage
+ var/internal_bleeding_chance
+ /// If we let off blood when hit, the max blood lost is this * the incoming damage
+ var/internal_bleeding_coefficient
+
+/datum/wound/pierce/wound_injury(datum/wound/old_wound)
+ blood_flow = initial_flow
+
+/datum/wound/pierce/receive_damage(wounding_type, wounding_dmg, wound_bonus)
+ if(victim.stat == DEAD || wounding_dmg < 5)
+ return
+ if(victim.blood_volume && prob(internal_bleeding_chance + wounding_dmg))
+ if(limb.current_gauze && limb.current_gauze.splint_factor)
+ wounding_dmg *= (1 - limb.current_gauze.splint_factor)
+ var/blood_bled = rand(1, wounding_dmg * internal_bleeding_coefficient) // 12 brute toolbox can cause up to 15/18/21 bloodloss on mod/sev/crit
+ switch(blood_bled)
+ if(1 to 6)
+ victim.bleed(blood_bled, TRUE)
+ if(7 to 13)
+ victim.visible_message("Blood droplets fly from the hole in [victim]'s [limb.name].", "You cough up a bit of blood from the blow to your [limb.name].", vision_distance=COMBAT_MESSAGE_RANGE)
+ victim.bleed(blood_bled, TRUE)
+ if(14 to 19)
+ victim.visible_message("A small stream of blood spurts from the hole in [victim]'s [limb.name]!", "You spit out a string of blood from the blow to your [limb.name]!", vision_distance=COMBAT_MESSAGE_RANGE)
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
+ victim.bleed(blood_bled)
+ if(20 to INFINITY)
+ victim.visible_message("A spray of blood streams from the gash in [victim]'s [limb.name]!", "You choke up on a spray of blood from the blow to your [limb.name]!", vision_distance=COMBAT_MESSAGE_RANGE)
+ victim.bleed(blood_bled)
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
+ victim.add_splatter_floor(get_step(victim.loc, victim.dir))
+
+/datum/wound/pierce/handle_process()
+ blood_flow = min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)
+
+ if(victim.bodytemperature < (BODYTEMP_NORMAL - 10))
+ blood_flow -= 0.2
+ if(prob(5))
+ to_chat(victim, "You feel the [lowertext(name)] in your [limb.name] firming up from the cold!")
+
+ if(victim.reagents?.has_reagent(/datum/reagent/toxin/heparin))
+ blood_flow += 0.5 // old herapin used to just add +2 bleed stacks per tick, this adds 0.5 bleed flow to all open cuts which is probably even stronger as long as you can cut them first
+
+ if(limb.current_gauze)
+ blood_flow -= limb.current_gauze.absorption_rate * gauzed_clot_rate
+ limb.current_gauze.absorption_capacity -= limb.current_gauze.absorption_rate
+
+ if(blood_flow <= 0)
+ qdel(src)
+
+/datum/wound/pierce/on_stasis()
+ . = ..()
+ if(blood_flow <= 0)
+ qdel(src)
+
+/datum/wound/pierce/treat(obj/item/I, mob/user)
+ if(istype(I, /obj/item/stack/medical/suture))
+ suture(I, user)
+ else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature() > 300)
+ tool_cauterize(I, user)
+
+/datum/wound/pierce/on_xadone(power)
+ . = ..()
+ blood_flow -= 0.03 * power // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort
+
+/datum/wound/pierce/on_synthflesh(power)
+ . = ..()
+ blood_flow -= 0.05 * power // 20u * 0.05 = -1 blood flow, less than with slashes but still good considering smaller bleed rates
+
+/// If someone is using a suture to close this cut
+/datum/wound/pierce/proc/suture(obj/item/stack/medical/suture/I, mob/user)
+ var/self_penalty_mult = (user == victim ? 1.4 : 1)
+ user.visible_message("[user] begins stitching [victim]'s [limb.name] with [I]...", "You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
+ if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
+ return
+ user.visible_message("[user] stitches up some of the bleeding on [victim].", "You stitch up some of the bleeding on [user == victim ? "yourself" : "[victim]"].")
+ var/blood_sutured = I.stop_bleeding / self_penalty_mult * 0.5
+ blood_flow -= blood_sutured
+ limb.heal_damage(I.heal_brute, I.heal_burn)
+
+ if(blood_flow > 0)
+ try_treating(I, user)
+ else
+ to_chat(user, "You successfully close the hole in [user == victim ? "your" : "[victim]'s"] [limb.name].")
+
+/// If someone is using either a cautery tool or something with heat to cauterize this pierce
+/datum/wound/pierce/proc/tool_cauterize(obj/item/I, mob/user)
+ var/self_penalty_mult = (user == victim ? 1.5 : 1)
+ user.visible_message("[user] begins cauterizing [victim]'s [limb.name] with [I]...", "You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
+ if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
+ return
+
+ user.visible_message("[user] cauterizes some of the bleeding on [victim].", "You cauterize some of the bleeding on [victim].")
+ limb.receive_damage(burn = 2 + severity, wound_bonus = CANT_WOUND)
+ if(prob(30))
+ victim.emote("scream")
+ var/blood_cauterized = (0.6 / self_penalty_mult) * 0.5
+ blood_flow -= blood_cauterized
+
+ if(blood_flow > 0)
+ try_treating(I, user)
+
+/datum/wound/pierce/moderate
+ name = "Minor Breakage"
+ desc = "Patient's skin has been broken open, causing severe bruising and minor internal bleeding in affected area."
+ treat_text = "Treat affected site with bandaging or exposure to extreme cold. In dire cases, brief exposure to vacuum may suffice." // space is cold in ss13, so it's like an ice pack!
+ examine_desc = "has a small, circular hole, gently bleeding"
+ occur_text = "spurts out a thin stream of blood"
+ sound_effect = 'sound/effects/wounds/pierce1.ogg'
+ severity = WOUND_SEVERITY_MODERATE
+ initial_flow = 1.5
+ gauzed_clot_rate = 0.8
+ internal_bleeding_chance = 30
+ internal_bleeding_coefficient = 1.25
+ threshold_minimum = 40
+ threshold_penalty = 15
+ status_effect_type = /datum/status_effect/wound/pierce/moderate
+ scar_keyword = "piercemoderate"
+
+/datum/wound/pierce/severe
+ name = "Open Puncture"
+ desc = "Patient's internal tissue is penetrated, causing sizeable internal bleeding and reduced limb stability."
+ treat_text = "Repair punctures in skin by suture or cautery, extreme cold may also work."
+ examine_desc = "is pierced clear through, with bits of tissue obscuring the open hole"
+ occur_text = "looses a violent spray of blood, revealing a pierced wound"
+ sound_effect = 'sound/effects/wounds/pierce2.ogg'
+ severity = WOUND_SEVERITY_SEVERE
+ initial_flow = 2.25
+ gauzed_clot_rate = 0.6
+ internal_bleeding_chance = 60
+ internal_bleeding_coefficient = 1.5
+ threshold_minimum = 60
+ threshold_penalty = 25
+ status_effect_type = /datum/status_effect/wound/pierce/severe
+ scar_keyword = "piercesevere"
+
+/datum/wound/pierce/critical
+ name = "Ruptured Cavity"
+ desc = "Patient's internal tissue and circulatory system is shredded, causing significant internal bleeding and damage to internal organs."
+ treat_text = "Surgical repair of puncture wound, followed by supervised resanguination."
+ examine_desc = "is ripped clear through, barely held together by exposed bone"
+ occur_text = "blasts apart, sending chunks of viscera flying in all directions"
+ sound_effect = 'sound/effects/wounds/pierce3.ogg'
+ severity = WOUND_SEVERITY_CRITICAL
+ initial_flow = 3
+ gauzed_clot_rate = 0.4
+ internal_bleeding_chance = 80
+ internal_bleeding_coefficient = 1.75
+ threshold_minimum = 110
+ threshold_penalty = 40
+ status_effect_type = /datum/status_effect/wound/pierce/critical
+ scar_keyword = "piercecritical"
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH)
diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm
new file mode 100644
index 0000000000..9044835272
--- /dev/null
+++ b/code/datums/wounds/slash.dm
@@ -0,0 +1,300 @@
+/*
+ Cuts
+*/
+
+/datum/wound/slash
+ sound_effect = 'sound/weapons/slice.ogg'
+ processes = TRUE
+ wound_type = WOUND_SLASH
+ treatable_by = list(/obj/item/stack/medical/suture)
+ treatable_by_grabbed = list(/obj/item/gun/energy/laser)
+ treatable_tool = TOOL_CAUTERY
+ base_treat_time = 3 SECONDS
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE)
+
+ /// How much blood we start losing when this wound is first applied
+ var/initial_flow
+ /// When we have less than this amount of flow, either from treatment or clotting, we demote to a lower cut or are healed of the wound
+ var/minimum_flow
+ /// How fast our blood flow will naturally decrease per tick, not only do larger cuts bleed more faster, they clot slower
+ var/clot_rate
+
+ /// Once the blood flow drops below minimum_flow, we demote it to this type of wound. If there's none, we're all better
+ var/demotes_to
+
+ /// How much staunching per type (cautery, suturing, bandaging) you can have before that type is no longer effective for this cut NOT IMPLEMENTED
+ var/max_per_type
+ /// The maximum flow we've had so far
+ var/highest_flow
+
+ /// A bad system I'm using to track the worst scar we earned (since we can demote, we want the biggest our wound has been, not what it was when it was cured (probably moderate))
+ var/datum/scar/highest_scar
+
+/datum/wound/slash/wound_injury(datum/wound/slash/old_wound = null)
+ blood_flow = initial_flow
+ if(old_wound)
+ blood_flow = max(old_wound.blood_flow, initial_flow)
+ if(old_wound.severity > severity && old_wound.highest_scar)
+ highest_scar = old_wound.highest_scar
+ old_wound.highest_scar = null
+
+ if(!highest_scar)
+ highest_scar = new
+ highest_scar.generate(limb, src, add_to_scars=FALSE)
+
+/datum/wound/slash/remove_wound(ignore_limb, replaced)
+ if(!replaced && highest_scar)
+ already_scarred = TRUE
+ highest_scar.lazy_attach(limb)
+ return ..()
+
+/datum/wound/slash/get_examine_description(mob/user)
+ if(!limb.current_gauze)
+ return ..()
+
+ var/list/msg = list("The cuts on [victim.p_their()] [limb.name] are wrapped with")
+ // how much life we have left in these bandages
+ switch(limb.current_gauze.absorption_capacity)
+ if(0 to 1.25)
+ msg += "nearly ruined "
+ if(1.25 to 2.75)
+ msg += "badly worn "
+ if(2.75 to 4)
+ msg += "slightly bloodied "
+ if(4 to INFINITY)
+ msg += "clean "
+ msg += "[limb.current_gauze.name]!"
+
+ return "[msg.Join()]"
+
+/datum/wound/slash/receive_damage(wounding_type, wounding_dmg, wound_bonus)
+ if(victim.stat != DEAD && wounding_type == WOUND_SLASH) // can't stab dead bodies to make it bleed faster this way
+ blood_flow += 0.05 * wounding_dmg
+
+/datum/wound/slash/drag_bleed_amount()
+ // say we have 3 severe cuts with 3 blood flow each, pretty reasonable
+ // compare with being at 100 brute damage before, where you bled (brute/100 * 2), = 2 blood per tile
+ var/bleed_amt = min(blood_flow * 0.1, 1) // 3 * 3 * 0.1 = 0.9 blood total, less than before! the share here is .3 blood of course.
+
+ if(limb.current_gauze) // gauze stops all bleeding from dragging on this limb, but wears the gauze out quicker
+ limb.seep_gauze(bleed_amt * 0.33)
+ return
+
+ return bleed_amt
+
+/datum/wound/slash/handle_process()
+ if(victim.stat == DEAD)
+ blood_flow -= max(clot_rate, WOUND_SLASH_DEAD_CLOT_MIN)
+ if(blood_flow < minimum_flow)
+ if(demotes_to)
+ replace_wound(demotes_to)
+ return
+ qdel(src)
+ return
+
+ blood_flow = min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)
+
+ if(victim.reagents?.has_reagent(/datum/reagent/toxin/heparin))
+ blood_flow += 0.5 // old herapin used to just add +2 bleed stacks per tick, this adds 0.5 bleed flow to all open cuts which is probably even stronger as long as you can cut them first
+
+ if(limb.current_gauze)
+ if(clot_rate > 0)
+ blood_flow -= clot_rate
+ blood_flow -= limb.current_gauze.absorption_rate
+ limb.seep_gauze(limb.current_gauze.absorption_rate)
+ else
+ blood_flow -= clot_rate
+
+ if(blood_flow > highest_flow)
+ highest_flow = blood_flow
+
+ if(blood_flow < minimum_flow)
+ if(demotes_to)
+ replace_wound(demotes_to)
+ else
+ to_chat(victim, "The cut on your [limb.name] has stopped bleeding!")
+ qdel(src)
+
+
+/datum/wound/slash/on_stasis()
+ if(blood_flow >= minimum_flow)
+ return
+ if(demotes_to)
+ replace_wound(demotes_to)
+ return
+ qdel(src)
+
+/* BEWARE, THE BELOW NONSENSE IS MADNESS. bones.dm looks more like what I have in mind and is sufficiently clean, don't pay attention to this messiness */
+
+/datum/wound/slash/check_grab_treatments(obj/item/I, mob/user)
+ if(istype(I, /obj/item/gun/energy/laser))
+ return TRUE
+
+/datum/wound/slash/treat(obj/item/I, mob/user)
+ if(istype(I, /obj/item/gun/energy/laser))
+ las_cauterize(I, user)
+ else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature() > 300)
+ tool_cauterize(I, user)
+ else if(istype(I, /obj/item/stack/medical/suture))
+ suture(I, user)
+
+/datum/wound/slash/try_handling(mob/living/carbon/human/user)
+ if(user.pulling != victim || user.zone_selected != limb.body_zone || user.a_intent == INTENT_GRAB)
+ return FALSE
+
+ if(!iscatperson(user))
+ return FALSE
+
+ lick_wounds(user)
+ return TRUE
+
+/// if a felinid is licking this cut to reduce bleeding
+/datum/wound/slash/proc/lick_wounds(mob/living/carbon/human/user)
+ if(INTERACTING_WITH(user, victim))
+ to_chat(user, "You're already interacting with [victim]!")
+ return
+
+ if(user.is_mouth_covered())
+ to_chat(user, "Your mouth is covered, you can't lick [victim]'s wounds!")
+ return
+
+ if(!user.getorganslot(ORGAN_SLOT_TONGUE))
+ to_chat(user, "You can't lick wounds without a tongue!") // f in chat
+ return
+
+ // transmission is one way patient -> felinid since google said cat saliva is antiseptic or whatever, and also because felinids are already risking getting beaten for this even without people suspecting they're spreading a deathvirus
+ for(var/datum/disease/D in victim.diseases)
+ user.ForceContractDisease(D)
+
+ user.visible_message("[user] begins licking the wounds on [victim]'s [limb.name].", "You begin licking the wounds on [victim]'s [limb.name]...", ignored_mobs=victim)
+ to_chat(victim, "[user] begins to lick the wounds on your [limb.name].[user] licks the wounds on [victim]'s [limb.name].", "You lick some of the wounds on [victim]'s [limb.name]", ignored_mobs=victim)
+ to_chat(victim, "[user] licks the wounds on your [limb.name]! minimum_flow)
+ try_handling(user)
+ else if(demotes_to)
+ to_chat(user, "You successfully lower the severity of [victim]'s cuts.")
+
+/datum/wound/slash/on_xadone(power)
+ . = ..()
+ blood_flow -= 0.03 * power // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort
+
+/datum/wound/slash/on_synthflesh(power)
+ . = ..()
+ blood_flow -= 0.075 * power // 20u * 0.075 = -1.5 blood flow, pretty good for how little effort it is
+
+/// If someone's putting a laser gun up to our cut to cauterize it
+/datum/wound/slash/proc/las_cauterize(obj/item/gun/energy/laser/lasgun, mob/user)
+ var/self_penalty_mult = (user == victim ? 1.25 : 1)
+ user.visible_message("[user] begins aiming [lasgun] directly at [victim]'s [limb.name]...", "You begin aiming [lasgun] directly at [user == victim ? "your" : "[victim]'s"] [limb.name]...")
+ if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
+ return
+ var/damage = lasgun.chambered.BB.damage
+ lasgun.chambered.BB.wound_bonus -= 30
+ lasgun.chambered.BB.damage *= self_penalty_mult
+ if(!lasgun.process_fire(victim, victim, TRUE, null, limb.body_zone))
+ return
+ victim.emote("scream")
+ blood_flow -= damage / (5 * self_penalty_mult) // 20 / 5 = 4 bloodflow removed, p good
+ victim.visible_message("The cuts on [victim]'s [limb.name] scar over!")
+
+/// If someone is using either a cautery tool or something with heat to cauterize this cut
+/datum/wound/slash/proc/tool_cauterize(obj/item/I, mob/user)
+ var/self_penalty_mult = (user == victim ? 1.5 : 1)
+ user.visible_message("[user] begins cauterizing [victim]'s [limb.name] with [I]...", "You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
+ if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
+ return
+
+ user.visible_message("[user] cauterizes some of the bleeding on [victim].", "You cauterize some of the bleeding on [victim].")
+ limb.receive_damage(burn = 2 + severity, wound_bonus = CANT_WOUND)
+ if(prob(30))
+ victim.emote("scream")
+ var/blood_cauterized = (0.6 / self_penalty_mult)
+ blood_flow -= blood_cauterized
+
+ if(blood_flow > minimum_flow)
+ try_treating(I, user)
+ else if(demotes_to)
+ to_chat(user, "You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts.")
+
+/// If someone is using a suture to close this cut
+/datum/wound/slash/proc/suture(obj/item/stack/medical/suture/I, mob/user)
+ var/self_penalty_mult = (user == victim ? 1.4 : 1)
+ user.visible_message("[user] begins stitching [victim]'s [limb.name] with [I]...", "You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
+
+ if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
+ return
+ user.visible_message("[user] stitches up some of the bleeding on [victim].", "You stitch up some of the bleeding on [user == victim ? "yourself" : "[victim]"].")
+ var/blood_sutured = I.stop_bleeding / self_penalty_mult
+ blood_flow -= blood_sutured
+ limb.heal_damage(I.heal_brute, I.heal_burn)
+
+ if(blood_flow > minimum_flow)
+ try_treating(I, user)
+ else if(demotes_to)
+ to_chat(user, "You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts.")
+
+
+/datum/wound/slash/moderate
+ name = "Rough Abrasion"
+ desc = "Patient's skin has been badly scraped, generating moderate blood loss."
+ treat_text = "Application of clean bandages or first-aid grade sutures, followed by food and rest."
+ examine_desc = "has an open cut"
+ occur_text = "is cut open, slowly leaking blood"
+ sound_effect = 'sound/effects/wounds/blood1.ogg'
+ severity = WOUND_SEVERITY_MODERATE
+ initial_flow = 1.5
+ minimum_flow = 0.375
+ max_per_type = 3
+ clot_rate = 0.12
+ threshold_minimum = 30
+ threshold_penalty = 10
+ status_effect_type = /datum/status_effect/wound/slash/moderate
+ scar_keyword = "slashmoderate"
+
+/datum/wound/slash/severe
+ name = "Open Laceration"
+ desc = "Patient's skin is ripped clean open, allowing significant blood loss."
+ treat_text = "Speedy application of first-aid grade sutures and clean bandages, followed by vitals monitoring to ensure recovery."
+ examine_desc = "has a severe cut"
+ occur_text = "is ripped open, veins spurting blood"
+ sound_effect = 'sound/effects/wounds/blood2.ogg'
+ severity = WOUND_SEVERITY_SEVERE
+ initial_flow = 2.4375
+ minimum_flow = 2.0625
+ clot_rate = 0.07
+ max_per_type = 4
+ threshold_minimum = 60
+ threshold_penalty = 25
+ demotes_to = /datum/wound/slash/moderate
+ status_effect_type = /datum/status_effect/wound/slash/severe
+ scar_keyword = "slashsevere"
+
+/datum/wound/slash/critical
+ name = "Weeping Avulsion"
+ desc = "Patient's skin is completely torn open, along with significant loss of tissue. Extreme blood loss will lead to quick death without intervention."
+ treat_text = "Immediate bandaging and either suturing or cauterization, followed by supervised resanguination."
+ examine_desc = "is carved down to the bone, spraying blood wildly"
+ occur_text = "is torn open, spraying blood wildly"
+ sound_effect = 'sound/effects/wounds/blood3.ogg'
+ severity = WOUND_SEVERITY_CRITICAL
+ initial_flow = 3.1875
+ minimum_flow = 3
+ clot_rate = -0.05 // critical cuts actively get worse instead of better
+ max_per_type = 5
+ threshold_minimum = 90
+ threshold_penalty = 40
+ demotes_to = /datum/wound/slash/severe
+ status_effect_type = /datum/status_effect/wound/slash/critical
+ scar_keyword = "slashcritical"
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH)
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index dabbe93bec..672d5c096f 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -16,6 +16,8 @@
/// If it's valid territory for gangs/cults to summon
var/valid_territory = TRUE
+ /// malf ais can hack this
+ var/valid_malf_hack = TRUE
/// if blobs can spawn there and if it counts towards their score.
var/blob_allowed = TRUE
/// whether servants can warp into this area from Reebe
@@ -516,7 +518,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
used_environ += amount
-/area/Entered(atom/movable/M)
+/area/Entered(atom/movable/M, atom/OldLoc)
set waitfor = FALSE
SEND_SIGNAL(src, COMSIG_AREA_ENTERED, M)
SEND_SIGNAL(M, COMSIG_ENTER_AREA, src) //The atom that enters the area
@@ -524,6 +526,11 @@ GLOBAL_LIST_EMPTY(teleportlocs)
return
var/mob/living/L = M
+ var/turf/oldTurf = get_turf(OldLoc)
+ var/area/A = oldTurf?.loc
+ if(A && (A.has_gravity != has_gravity))
+ L.update_gravity(L.mob_has_gravity())
+
if(!L.ckey)
return
@@ -567,6 +574,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
power_environ = FALSE
always_unpowered = FALSE
valid_territory = FALSE
+ valid_malf_hack = FALSE
blob_allowed = FALSE
addSorted()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 5d29ef1598..213f88604a 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -70,6 +70,13 @@
/// A luminescence-shifted value of the last color calculated for chatmessage overlays
var/chat_color_darkened
+ //skyrat edit - custom examine icon
+ var/examine_icon
+ var/examine_icon_state
+ //
+ ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy()
+ var/list/targeted_by
+
/atom/New(loc, ...)
//atom creation method that preloads variables at creation
if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New()
@@ -101,6 +108,8 @@
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
+ if(loc)
+ SEND_SIGNAL(loc, COMSIG_ATOM_CREATED, src) /// Sends a signal that the new atom `src`, has been created at `loc`
//atom color stuff
if(color)
add_atom_colour(color, FIXED_COLOUR_PRIORITY)
@@ -142,6 +151,11 @@
LAZYCLEARLIST(overlays)
LAZYCLEARLIST(priority_overlays)
+ for(var/i in targeted_by)
+ var/mob/M = i
+ LAZYREMOVE(M.do_afters, src)
+ targeted_by = null
+
QDEL_NULL(light)
return ..()
@@ -217,7 +231,7 @@
/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE)
SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user)
if(does_attack_animation)
- user.changeNext_move(CLICK_CD_MELEE)
+ user.DelayNextAction(CLICK_CD_MELEE)
log_combat(user, src, "punched", "hulk powers")
user.do_attack_animation(src, ATTACK_EFFECT_SMASH)
@@ -329,8 +343,10 @@
. = override.Join("")
///Generate the full examine string of this atom (including icon for goonchat)
+//skyrat change - custom examine icons
/atom/proc/get_examine_string(mob/user, thats = FALSE)
- return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]"
+ return "[icon2html(examine_icon ? examine_icon : src, user, examine_icon_state ? examine_icon_state : icon_state)] [thats? "That's ":""][get_examine_name(user)]"
+//end changes (yeah the whole proc was modified)
/atom/proc/examine(mob/user)
. = list("[get_examine_string(user, TRUE)].")
@@ -741,7 +757,7 @@
flags_1 |= ADMIN_SPAWNED_1
. = ..()
switch(var_name)
- if("color")
+ if(NAMEOF(src, color))
add_atom_colour(color, ADMIN_COLOUR_PRIORITY)
/atom/vv_get_dropdown()
@@ -930,6 +946,8 @@
log_game(log_text)
if(LOG_GAME)
log_game(log_text)
+ if(LOG_SHUTTLE)
+ log_shuttle(log_text)
else
stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).")
log_game(log_text)
@@ -950,15 +968,16 @@
if(source != target)
target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE)
-/*
-Proc for attack log creation, because really why not
-1 argument is the actor performing the action
-2 argument is the target of the action
-3 is a verb describing the action (e.g. punched, throwed, kicked, etc.)
-4 is a tool with which the action was made (usually an item)
-5 is any additional text, which will be appended to the rest of the log line
-*/
-
+/**
+ * Log a combat message in the attack log
+ *
+ * Arguments:
+ * * atom/user - argument is the actor performing the action
+ * * atom/target - argument is the target of the action
+ * * what_done - is a verb describing the action (e.g. punched, throwed, kicked, etc.)
+ * * atom/object - is a tool with which the action was made (usually an item)
+ * * addition - is any additional text, which will be appended to the rest of the log line
+ */
/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
var/ssource = key_name(user)
var/starget = key_name(target)
@@ -982,6 +1001,39 @@ Proc for attack log creation, because really why not
var/reverse_message = "has been [what_done] by [ssource][postfix]"
target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE)
+/**
+ * log_wound() is for when someone is *attacked* and suffers a wound. Note that this only captures wounds from damage, so smites/forced wounds aren't logged, as well as demotions like cuts scabbing over
+ *
+ * Note that this has no info on the attack that dealt the wound: information about where damage came from isn't passed to the bodypart's damaged proc. When in doubt, check the attack log for attacks at that same time
+ * TODO later: Add logging for healed wounds, though that will require some rewriting of healing code to prevent admin heals from spamming the logs. Not high priority
+ *
+ * Arguments:
+ * * victim- The guy who got wounded
+ * * suffered_wound- The wound, already applied, that we're logging. It has to already be attached so we can get the limb from it
+ * * dealt_damage- How much damage is associated with the attack that dealt with this wound.
+ * * dealt_wound_bonus- The wound_bonus, if one was specified, of the wounding attack
+ * * dealt_bare_wound_bonus- The bare_wound_bonus, if one was specified *and applied*, of the wounding attack. Not shown if armor was present
+ * * base_roll- Base wounding ability of an attack is a random number from 1 to (dealt_damage ** WOUND_DAMAGE_EXPONENT). This is the number that was rolled in there, before mods
+ */
+/proc/log_wound(atom/victim, datum/wound/suffered_wound, dealt_damage, dealt_wound_bonus, dealt_bare_wound_bonus, base_roll)
+ if(QDELETED(victim) || !suffered_wound)
+ return
+ var/message = "has suffered: [suffered_wound][suffered_wound.limb ? " to [suffered_wound.limb.name]" : null]"// maybe indicate if it's a promote/demote?
+
+ if(dealt_damage)
+ message += " | Damage: [dealt_damage]"
+ // The base roll is useful since it can show how lucky someone got with the given attack. For example, dealing a cut
+ if(base_roll)
+ message += " (rolled [base_roll]/[dealt_damage ** WOUND_DAMAGE_EXPONENT])"
+
+ if(dealt_wound_bonus)
+ message += " | WB: [dealt_wound_bonus]"
+
+ if(dealt_bare_wound_bonus)
+ message += " | BWB: [dealt_bare_wound_bonus]"
+
+ victim.log_message(message, LOG_ATTACK, color="blue")
+
// Filter stuff
/atom/proc/add_filter(name,priority,list/params)
LAZYINITLIST(filter_data)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 9736f473e8..dab44cfe3e 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -16,6 +16,7 @@
var/verb_ask = "asks"
var/verb_exclaim = "exclaims"
var/verb_whisper = "whispers"
+ var/verb_sing = "sings" // Skyrat edit
var/verb_yell = "yells"
var/speech_span
var/inertia_dir = 0
@@ -120,25 +121,25 @@
if((var_name in careful_edits) && (var_value % world.icon_size) != 0)
return FALSE
switch(var_name)
- if("x")
+ if(NAMEOF(src, x))
var/turf/T = locate(var_value, y, z)
if(T)
forceMove(T)
return TRUE
return FALSE
- if("y")
+ if(NAMEOF(src, y))
var/turf/T = locate(x, var_value, z)
if(T)
forceMove(T)
return TRUE
return FALSE
- if("z")
+ if(NAMEOF(src, z))
var/turf/T = locate(x, y, var_value)
if(T)
forceMove(T)
return TRUE
return FALSE
- if("loc")
+ if(NAMEOF(src, loc))
if(istype(var_value, /atom))
forceMove(var_value)
return TRUE
diff --git a/code/game/atoms_movement.dm b/code/game/atoms_movement.dm
index 68db17f076..db9424d983 100644
--- a/code/game/atoms_movement.dm
+++ b/code/game/atoms_movement.dm
@@ -6,6 +6,7 @@
// To be removed on step_ conversion
// All this work to prevent a second bump
/atom/movable/Move(atom/newloc, direct=0)
+ set waitfor = FALSE //n o
. = FALSE
if(!newloc || newloc == loc)
return
@@ -52,6 +53,7 @@
////////////////////////////////////////
/atom/movable/Move(atom/newloc, direct)
+ set waitfor = FALSE //n o
var/atom/movable/pullee = pulling
var/turf/T = loc
if(!moving_from_pull)
diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm
index 7b798f19e4..406e142a25 100644
--- a/code/game/gamemodes/clock_cult/clock_cult.dm
+++ b/code/game/gamemodes/clock_cult/clock_cult.dm
@@ -38,7 +38,8 @@ Credit where due:
5. Xhuis from /tg/ for coding the first iteration of the mode, and the new, reworked version
6. ChangelingRain from /tg/ for maintaining the gamemode for months after its release prior to its rework
7. Clockwork cult code as of now, at least the one being pulled from Citadel Station's master branch, is being, or already is, fixed by Coolgat3 and Avunia.
-
+8. Modern clockwork cult code mixed with original clockwork code, with various changes to make it less of a fustercluck, done by KeRSe. \
+ Fixes and assistance done by TimothyTeakettle, Kevinz000, and Deltafire15. -Very glad for the help they gave.
*/
///////////
@@ -133,7 +134,7 @@ Credit where due:
config_tag = "clockwork_cult"
antag_flag = ROLE_SERVANT_OF_RATVAR
false_report_weight = 10
- required_players = 35
+ required_players = 24 //Fixing this directly for now since apparently config machine for forcing modes broke.
required_enemies = 3
recommended_enemies = 5
enemy_minimum_age = 7
@@ -143,13 +144,12 @@ Credit where due:
announce_text = "Servants of Ratvar are trying to summon the Justiciar!\n\
Servants: Construct defenses to protect the Ark. Sabotage the station!\n\
Crew: Stop the servants before they can summon the Clockwork Justiciar."
- var/servants_to_serve = list()
+ var/list/servants_to_serve = list() //Yes this list is made out of list
var/roundstart_player_count
- var/ark_time //In minutes, how long the Ark waits before activation; this is equal to 30 + (number of players / 5) (max 40 mins.)
var/datum/team/clockcult/main_clockcult
-/datum/game_mode/clockwork_cult/pre_setup()
+/datum/game_mode/clockwork_cult/pre_setup() //Gamemode and job code is pain. Have fun codediving all of that stuff, whoever works on this next - Delta
var/list/errorList = list()
var/list/reebes = SSmapping.LoadGroup(errorList, "Reebe", "map_files/generic", "City_of_Cogs.dmm", default_traits = ZTRAITS_REEBE, silent = TRUE)
if(errorList.len) // reebe failed to load
@@ -162,38 +162,36 @@ Credit where due:
restricted_jobs += protected_jobs
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
restricted_jobs += "Assistant"
- var/starter_servants = 4 //Guaranteed four servants
+ var/starter_servants = 4 //Try to go for at least four
var/number_players = num_players()
roundstart_player_count = number_players
if(number_players > 30) //plus one servant for every additional 10 players above 30
number_players -= 30
starter_servants += round(number_players / 10)
- starter_servants = min(starter_servants, 8) //max 8 servants (that sould only happen with a ton of players)
- GLOB.clockwork_vitality += 50 * starter_servants //some starter Vitality to help recover from initial fuck ups
+ starter_servants = min(starter_servants, 8) //max 8 servants (that sould only happen with a ton of players)
while(starter_servants)
+ if(!antag_candidates.len)
+ break //Skip setup, DO NOT RUNTIME
var/datum/mind/servant = antag_pick(antag_candidates)
servants_to_serve += servant
antag_candidates -= servant
- servant.assigned_role = ROLE_SERVANT_OF_RATVAR
servant.special_role = ROLE_SERVANT_OF_RATVAR
+ servant.restricted_roles = restricted_jobs
starter_servants--
- ark_time = 30 + round((roundstart_player_count / 5)) //In minutes, how long the Ark will wait before activation
- ark_time = min(ark_time, 35) //35 minute maximum for the activation timer
- return 1
+ if(!servants_to_serve.len) //Uh oh, something went wrong
+ setup_error = "There are no clockcult candidates! (Or something went very wrong)"
+ return FALSE
+ GLOB.clockwork_vitality += 50 * servants_to_serve.len //some starter Vitality to help recover from initial fuck ups
+ return TRUE //Haha yes it works time to not touch it any more than that.
/datum/game_mode/clockwork_cult/post_setup()
for(var/S in servants_to_serve)
var/datum/mind/servant = S
log_game("[key_name(servant)] was made an initial servant of Ratvar")
var/mob/living/L = servant.current
- var/turf/T = pick(GLOB.servant_spawns)
- L.forceMove(T)
- GLOB.servant_spawns -= T
greet_servant(L)
equip_servant(L)
add_servant_of_ratvar(L, TRUE)
- var/obj/structure/destructible/clockwork/massive/celestial_gateway/G = GLOB.ark_of_the_clockwork_justiciar //that's a mouthful
- G.final_countdown(ark_time)
..()
return 1
@@ -201,22 +199,23 @@ Credit where due:
if(!M)
return 0
to_chat(M, "You are a servant of Ratvar, the Clockwork Justiciar!")
- to_chat(M, "You have approximately [ark_time] minutes until the Ark activates.")
- to_chat(M, "Unlock Script scripture by converting a new servant.")
- to_chat(M, "Application scripture will be unlocked halfway until the Ark's activation.")
+ to_chat(M, "Unlock Script scripture by converting a new servant or when 35kw of power is reached.")
+ to_chat(M, "Application scripture will be unlocked when 50kw of power is reached.")
M.playsound_local(get_turf(M), 'sound/ambience/antag/clockcultalr.ogg', 100, FALSE, pressure_affected = FALSE)
return 1
-/datum/game_mode/proc/equip_servant(mob/living/M) //Grants a clockwork slab to the mob, with one of each component
+/datum/game_mode/proc/equip_servant(mob/living/M) //Grants a clockwork slab to the mob
if(!M || !ishuman(M))
return FALSE
var/mob/living/carbon/human/L = M
- L.equipOutfit(/datum/outfit/servant_of_ratvar)
var/obj/item/clockwork/slab/S = new
var/slot = "At your feet"
- var/list/slots = list("In your left pocket" = SLOT_L_STORE, "In your right pocket" = SLOT_R_STORE, "In your backpack" = SLOT_IN_BACKPACK, "On your belt" = SLOT_BELT)
+ var/list/slots = list("In your left pocket" = SLOT_L_STORE, "In your right pocket" = SLOT_R_STORE, "In your backpack" = SLOT_IN_BACKPACK)
if(ishuman(L))
var/mob/living/carbon/human/H = L
+ var/obj/item/clockwork/replica_fabricator/F = new
+ if(H.equip_to_slot_or_del(F, SLOT_IN_BACKPACK))
+ to_chat(H, "You have been equipped with a replica fabricator, an advanced tool that can convert objects like doors, tables or even coats into clockwork equivalents.")
slot = H.equip_in_one_of_slots(S, slots)
if(slot == "In your backpack")
slot = "In your [H.back.name]"
@@ -224,7 +223,6 @@ Credit where due:
if(!S.forceMove(get_turf(L)))
qdel(S)
if(S && !QDELETED(S))
- to_chat(L, "There is a paper in your backpack! It'll tell you if anything's changed, as well as what to expect.")
to_chat(L, "[slot] is a clockwork slab, a multipurpose tool used to construct machines and invoke ancient words of power. If this is your first time \
as a servant, you can find a concise tutorial in the Recollection category of its interface.")
to_chat(L, "If you want more information, you can read the wiki page to learn more.")
@@ -278,7 +276,7 @@ Credit where due:
gloves = /obj/item/clothing/gloves/color/yellow
belt = /obj/item/storage/belt/utility/servant
backpack_contents = list(/obj/item/storage/box/engineer = 1, \
- /obj/item/clockwork/replica_fabricator = 1, /obj/item/stack/tile/brass/fifty = 1, /obj/item/paper/servant_primer = 1, /obj/item/reagent_containers/food/drinks/bottle/holyoil = 1)
+ /obj/item/clockwork/replica_fabricator = 1, /obj/item/stack/tile/brass/fifty = 1, /obj/item/reagent_containers/food/drinks/bottle/holyoil = 1)
id = /obj/item/pda
var/plasmaman //We use this to determine if we should activate internals in post_equip()
@@ -305,53 +303,3 @@ Credit where due:
PDA.update_label()
PDA.id_check(H, W)
H.sec_hud_set_ID()
-
-
-//This paper serves as a quick run-down to the cult as well as a changelog to refer to.
-//Check strings/clockwork_cult_changelog.txt for the changelog, and update it when you can!
-/obj/item/paper/servant_primer
- name = "The Ark And You: A Primer On Servitude"
- color = "#DAAA18"
- info = "DON'T PANIC.
\
- Here's a quick primer on what you should know here.\
- \
-
You're in a place called Reebe right now. The crew can't get here normally.
\
-
In the north is your base camp, with supplies, consoles, and the Ark. In the south is an inaccessible area that the crew can walk between \
- once they arrive (more on that later.) Everything between that space is an open area.
\
-
Your job as a servant is to build fortifications and defenses to protect the Ark and your base once the Ark activates. You can do this \
- however you like, but work with your allies and coordinate your efforts.
\
-
Once the Ark activates, the station will be alerted. Portals to Reebe will open up in nearly every room. When they take these portals, \
- the crewmembers will arrive in the area that you can't access, but can get through it freely - whereas you can't. Treat this as the \"spawn\" of the \
- crew and defend it accordingly.
\
- \
- \
- Here is the layout of Reebe, from left to right:\
-
\
-
Dressing Room: Contains clothing, a dresser, and a mirror. There are spare slabs and absconders here.
\
-
Listening Station: Contains intercoms, a telecomms relay, and a list of frequencies.
\
-
Ark Chamber: Houses the Ark.
\
-
Observation Room: Contains five camera observers. These can be used to watch the station through its cameras, as well as to teleport down \
- to most areas. To do this, use the Warp action while hovering over the tile you want to warp to.
\
-
Infirmary: Contains sleepers and basic medical supplies for superficial wounds. The sleepers can consume Vitality to heal any occupants. \
- This room is generally more useful during the preparation phase; when defending the Ark, scripture is more useful.
"
- dat += "Check Logs "
- dat += "Log Out "
- if(obj_flags & EMAGGED)
- dat += "WARNING: Logging functionality partially disabled from outside source. "
- dat += "Restore logging functionality? "
- else
- if(logs.len)
- for(var/entry in logs)
- dat += "[entry] "
- else
- dat += "No activity has been recorded at this time. "
- if(obj_flags & EMAGGED)
- dat += "@#%! CLEAR LOGS"
- dat += "Return"
- operator = user
- else
- dat = "Please swipe a valid ID to log in..."
- var/datum/browser/popup = new(user, "apc_control", name, 600, 400)
- popup.set_content(dat)
- popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
- popup.open()
+/obj/machinery/computer/apc_control/ui_interact(mob/user, datum/tgui/ui)
+ operator = user
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ApcControl")
+ ui.open()
-/obj/machinery/computer/apc_control/Topic(href, href_list)
+/obj/machinery/computer/apc_control/ui_data(mob/user)
+ var/list/data = list()
+ data["auth_id"] = auth_id
+ data["authenticated"] = authenticated
+ data["emagged"] = obj_flags & EMAGGED
+ data["logging"] = should_log
+ data["restoring"] = restoring
+ data["logs"] = list()
+ data["apcs"] = list()
+
+ for(var/entry in logs)
+ data["logs"] += list(list("entry" = entry))
+
+ for(var/apc in GLOB.apcs_list)
+ if(check_apc(apc))
+ var/obj/machinery/power/apc/A = apc
+ var/has_cell = (A.cell) ? TRUE : FALSE
+ data["apcs"] += list(list(
+ "name" = A.area.name,
+ "operating" = A.operating,
+ "charge" = (has_cell) ? A.cell.percent() : "NOCELL",
+ "load" = DisplayPower(A.lastused_total),
+ "charging" = A.charging,
+ "chargeMode" = A.chargemode,
+ "eqp" = A.equipment,
+ "lgt" = A.lighting,
+ "env" = A.environ,
+ "responds" = A.aidisabled || A.panel_open,
+ "ref" = REF(A)
+ )
+ )
+ return data
+
+/obj/machinery/computer/apc_control/ui_act(action, params)
if(..())
return
- if(!usr || !usr.canUseTopic(src) || stat || QDELETED(src))
- return
- if(href_list["authenticate"])
- var/obj/item/card/id/ID = usr.get_idcard(TRUE)
- if(ID && istype(ID))
- if(check_access(ID))
+ switch(action)
+ if("log-in")
+ if(obj_flags & EMAGGED)
authenticated = TRUE
- auth_id = "[ID.registered_name] ([ID.assignment])"
- log_activity("logged in")
- if(href_list["log_out"])
- log_activity("logged out")
- authenticated = FALSE
- auth_id = "\[NULL\]"
- if(href_list["restore_logging"])
- to_chat(usr, "[icon2html(src, usr)] Logging functionality restored from backup data.")
- obj_flags &= ~EMAGGED
- LAZYADD(logs, "-=- Logging restored to full functionality at this point -=-")
- if(href_list["access_apc"])
- playsound(src, "terminal_type", 50, 0)
- var/obj/machinery/power/apc/APC = locate(href_list["access_apc"]) in GLOB.apcs_list
- if(!APC || APC.aidisabled || APC.panel_open || QDELETED(APC))
- to_chat(usr, "[icon2html(src, usr)] APC does not return interface request. Remote access may be disabled.")
- return
- if(active_apc)
- to_chat(usr, "[icon2html(src, usr)] Disconnected from [active_apc].")
- active_apc.say("Remote access canceled. Interface locked.")
- playsound(active_apc, 'sound/machines/boltsdown.ogg', 25, 0)
- playsound(active_apc, 'sound/machines/terminal_alert.ogg', 50, 0)
- active_apc.locked = TRUE
- active_apc.update_icon()
- active_apc.remote_control = null
- active_apc = null
- to_chat(usr, "[icon2html(src, usr)] Connected to APC in [get_area_name(APC.area, TRUE)]. Interface request sent.")
- log_activity("remotely accessed APC in [get_area_name(APC.area, TRUE)]")
- APC.remote_control = src
- APC.ui_interact(usr)
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
- message_admins("[ADMIN_LOOKUPFLW(usr)] remotely accessed [APC] from [src] at [AREACOORD(src)].")
- log_game("[key_name(usr)] remotely accessed [APC] from [src] at [AREACOORD(src)].")
- if(APC.locked)
- APC.say("Remote access detected. Interface unlocked.")
- playsound(APC, 'sound/machines/boltsup.ogg', 25, 0)
- playsound(APC, 'sound/machines/terminal_alert.ogg', 50, 0)
- APC.locked = FALSE
- APC.update_icon()
- active_apc = APC
- if(href_list["name_filter"])
- playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
- var/new_filter = stripped_input(usr, "What name are you looking for?", name)
- if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src))
- return
- log_activity("changed name filter to \"[new_filter]\"")
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
- result_filters["Name"] = new_filter
- if(href_list["above_filter"])
- playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
- var/new_filter = input(usr, "Enter a percentage from 1-100 to sort by (greater than).", name) as null|num
- if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src))
- return
- log_activity("changed greater than charge filter to \"[new_filter]\"")
- if(new_filter)
- new_filter = clamp(new_filter, 0, 100)
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
- result_filters["Charge Above"] = new_filter
- if(href_list["below_filter"])
- playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
- var/new_filter = input(usr, "Enter a percentage from 1-100 to sort by (lesser than).", name) as null|num
- if(!src || !usr || !usr.canUseTopic(src) || stat || QDELETED(src))
- return
- log_activity("changed lesser than charge filter to \"[new_filter]\"")
- if(new_filter)
- new_filter = clamp(new_filter, 0, 100)
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
- result_filters["Charge Below"] = new_filter
- if(href_list["access_filter"])
- if(isnull(result_filters["Responsive"]))
- result_filters["Responsive"] = 1
- log_activity("sorted by non-responsive APCs only")
- else
- result_filters["Responsive"] = !result_filters["Responsive"]
- log_activity("sorted by all APCs")
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
- if(href_list["check_logs"])
- checking_logs = TRUE
- log_activity("checked logs")
- if(href_list["check_apcs"])
- checking_logs = FALSE
- log_activity("checked APCs")
- if(href_list["clear_logs"])
- logs = list()
- ui_interact(usr) //Refresh the UI after a filter changes
+ auth_id = "Unknown (Unknown):"
+ log_activity("[auth_id] logged in to the terminal")
+ return
+ var/obj/item/card/id/ID = operator.get_idcard(TRUE)
+ if(ID && istype(ID))
+ if(check_access(ID))
+ authenticated = TRUE
+ auth_id = "[ID.registered_name] ([ID.assignment]):"
+ log_activity("[auth_id] logged in to the terminal")
+ playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE)
+ else
+ auth_id = "[ID.registered_name] ([ID.assignment]):"
+ log_activity("[auth_id] attempted to log into the terminal")
+ return
+ auth_id = "Unknown (Unknown):"
+ log_activity("[auth_id] attempted to log into the terminal")
+ if("log-out")
+ log_activity("[auth_id] logged out of the terminal")
+ playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE)
+ authenticated = FALSE
+ auth_id = "\[NULL\]"
+ if("toggle-logs")
+ should_log = !should_log
+ log_game("[key_name(operator)] set the logs of [src] in [AREACOORD(src)] [should_log ? "On" : "Off"]")
+ if("restore-console")
+ restoring = TRUE
+ addtimer(CALLBACK(src, .proc/restore_comp), rand(3,5) * 9)
+ if("access-apc")
+ var/ref = params["ref"]
+ playsound(src, "terminal_type", 50, FALSE)
+ var/obj/machinery/power/apc/APC = locate(ref) in GLOB.apcs_list
+ if(!APC)
+ return
+ if(active_apc)
+ to_chat(operator, "[icon2html(src, auth_id)] Disconnected from [active_apc].")
+ active_apc.say("Remote access canceled. Interface locked.")
+ playsound(active_apc, 'sound/machines/boltsdown.ogg', 25, FALSE)
+ playsound(active_apc, 'sound/machines/terminal_alert.ogg', 50, FALSE)
+ active_apc.locked = TRUE
+ active_apc.update_icon()
+ active_apc.remote_control = null
+ active_apc = null
+ APC.remote_control = src
+ APC.ui_interact(operator)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
+ log_game("[key_name(operator)] remotely accessed [APC] from [src] at [AREACOORD(src)].")
+ log_activity("[auth_id] remotely accessed APC in [get_area_name(APC.area, TRUE)]")
+ if(APC.locked)
+ APC.say("Remote access detected. Interface unlocked.")
+ playsound(APC, 'sound/machines/boltsup.ogg', 25, FALSE)
+ playsound(APC, 'sound/machines/terminal_alert.ogg', 50, FALSE)
+ APC.locked = FALSE
+ APC.update_icon()
+ active_apc = APC
+ if("check-logs")
+ log_activity("Checked Logs")
+ if("check-apcs")
+ log_activity("Checked APCs")
+ if("toggle-minor")
+ var/ref = params["ref"]
+ var/type = params["type"]
+ var/value = params["value"]
+ var/obj/machinery/power/apc/target = locate(ref) in GLOB.apcs_list
+ if(!target)
+ return
+ target.vars[type] = target.setsubsystem(text2num(value))
+ target.update_icon()
+ target.update()
+ var/setTo = ""
+ switch(target.vars[type])
+ if(0)
+ setTo = "Off"
+ if(1)
+ setTo = "Auto Off"
+ if(2)
+ setTo = "On"
+ if(3)
+ setTo = "Auto On"
+ log_activity("Set APC [target.area.name] [type] to [setTo]")
+ log_game("[key_name(operator)] Set APC [target.area.name] [type] to [setTo]]")
+ if("breaker")
+ var/ref = params["ref"]
+ var/obj/machinery/power/apc/target = locate(ref) in GLOB.apcs_list
+ target.toggle_breaker()
+ var/setTo = target.operating ? "On" : "Off"
+ log_activity("Turned APC [target.area.name]'s breaker [setTo]")
/obj/machinery/computer/apc_control/emag_act(mob/user)
- . = ..()
- if(!authenticated)
- to_chat(user, "You bypass [src]'s access requirements using your emag.")
- authenticated = TRUE
- log_activity("logged in")
- else
- if(obj_flags & EMAGGED)
- return
- user.visible_message("You emag [src], disabling precise logging and allowing you to clear logs.")
- log_game("[key_name(user)] emagged [src] at [AREACOORD(src)], disabling operator tracking.")
- obj_flags |= EMAGGED
- playsound(src, "sparks", 50, 1)
- return TRUE
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ log_game("[key_name(user)] emagged [src] at [AREACOORD(src)]")
+ playsound(src, "sparks", 50, TRUE)
/obj/machinery/computer/apc_control/proc/log_activity(log_text)
- var/op_string = operator && !(obj_flags & EMAGGED) ? operator : "\[NULL OPERATOR\]"
- LAZYADD(logs, "([STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)]) [op_string] [log_text]")
+ if(!should_log)
+ return
+ LAZYADD(logs, "([STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)]): [auth_id] [log_text]")
+
+/obj/machinery/computer/apc_control/proc/restore_comp()
+ obj_flags &= ~EMAGGED
+ should_log = TRUE
+ log_game("[key_name(operator)] restored the logs of [src] in [AREACOORD(src)]")
+ log_activity("-=- Logging restored to full functionality at this point -=-")
+ restoring = FALSE
/mob/proc/using_power_flow_console()
for(var/obj/machinery/computer/apc_control/A in range(1, src))
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 8a91c266a0..4fb39c04e1 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -136,3 +136,17 @@
empprize = pickweight(prizes)
new empprize(loc)
explosion(loc, -1, 0, 1+num_of_prizes, flame_range = 1+num_of_prizes)
+
+/obj/machinery/computer/arcade/attackby(obj/item/O, mob/user, params)
+ if(istype(O, /obj/item/stack/arcadeticket))
+ var/obj/item/stack/arcadeticket/T = O
+ var/amount = T.get_amount()
+ if(amount <2)
+ to_chat(user, "You need 2 tickets to claim a prize!")
+ return
+ prizevend(user)
+ T.pay_tickets()
+ T.update_icon()
+ O = T
+ to_chat(user, "You turn in 2 tickets to the [src] and claim a prize!")
+ return
diff --git a/code/game/machinery/computer/arcade/misc_arcade.dm b/code/game/machinery/computer/arcade/misc_arcade.dm
index 78b4a6863c..24516740f9 100644
--- a/code/game/machinery/computer/arcade/misc_arcade.dm
+++ b/code/game/machinery/computer/arcade/misc_arcade.dm
@@ -8,7 +8,7 @@
icon_state = "arcade"
circuit = /obj/item/circuitboard/computer/arcade/amputation
-/obj/machinery/computer/arcade/amputation/attack_hand(mob/user)
+/obj/machinery/computer/arcade/amputation/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!iscarbon(user))
return
var/mob/living/carbon/c_user = user
@@ -28,4 +28,4 @@
for(var/i=1; i<=rand(3,5); i++)
prizevend(user)
else
- to_chat(c_user, "You (wisely) decide against putting your hand in the machine.")
\ No newline at end of file
+ to_chat(c_user, "You (wisely) decide against putting your hand in the machine.")
diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm
index 6df7120dcc..50a6d10302 100644
--- a/code/game/machinery/computer/atmos_alert.dm
+++ b/code/game/machinery/computer/atmos_alert.dm
@@ -19,11 +19,10 @@
SSradio.remove_object(src, receive_frequency)
return ..()
-/obj/machinery/computer/atmos_alert/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/atmos_alert/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "atmos_alert", name, 350, 300, master_ui, state)
+ ui = new(user, src, "AtmosAlertConsole", name)
ui.open()
/obj/machinery/computer/atmos_alert/ui_data(mob/user)
@@ -45,11 +44,11 @@
if("clear")
var/zone = params["zone"]
if(zone in priority_alarms)
- to_chat(usr, "Priority alarm for [zone] cleared.")
+ to_chat(usr, "Priority alarm for [zone] cleared.")
priority_alarms -= zone
. = TRUE
if(zone in minor_alarms)
- to_chat(usr, "Minor alarm for [zone] cleared.")
+ to_chat(usr, "Minor alarm for [zone] cleared.")
minor_alarms -= zone
. = TRUE
update_icon()
diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm
index 2faf76ca94..4ba8d9f3d3 100644
--- a/code/game/machinery/computer/atmos_control.dm
+++ b/code/game/machinery/computer/atmos_control.dm
@@ -91,8 +91,6 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers)
icon_screen = "tank"
icon_keyboard = "atmos_key"
circuit = /obj/item/circuitboard/computer/atmos_control
- ui_x = 400
- ui_y = 925
var/frequency = FREQ_ATMOS_STORAGE
var/list/sensors = list(
@@ -123,11 +121,10 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers)
SSradio.remove_object(src, frequency)
return ..()
-/obj/machinery/computer/atmos_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/atmos_control/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "atmos_control", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "AtmosControlConsole", name)
ui.open()
/obj/machinery/computer/atmos_control/ui_data(mob/user)
@@ -265,13 +262,6 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers)
for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices)
U.broadcast_status()
-/obj/machinery/computer/atmos_control/tank/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "atmos_control", name, ui_x, ui_y, master_ui, state)
- ui.open()
-
/obj/machinery/computer/atmos_control/tank/ui_data(mob/user)
var/list/data = ..()
data["tank"] = TRUE
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 70a59230b2..d42291cd3c 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -4,109 +4,163 @@
icon_screen = "cameras"
icon_keyboard = "security_key"
circuit = /obj/item/circuitboard/computer/security
- var/last_pic = 1
- var/list/network = list("ss13")
- var/list/watchers = list() //who's using the console, associated with the camera they're on.
-
light_color = LIGHT_COLOR_RED
+ var/list/network = list("ss13")
+ var/obj/machinery/camera/active_camera
+ var/list/concurrent_users = list()
+
+ // Stuff needed to render the map
+ var/map_name
+ var/const/default_map_size = 15
+ var/obj/screen/cam_screen
+ var/obj/screen/plane_master/lighting/cam_plane_master
+ var/obj/screen/background/cam_background
+
/obj/machinery/computer/security/Initialize()
. = ..()
+ // Map name has to start and end with an A-Z character,
+ // and definitely NOT with a square bracket or even a number.
+ // I wasted 6 hours on this. :agony:
+ map_name = "camera_console_[REF(src)]_map"
+ // Convert networks to lowercase
for(var/i in network)
network -= i
network += lowertext(i)
-
-/obj/machinery/computer/security/check_eye(mob/user)
- if(!can_interact(user) || !(user in watchers) || !watchers[user])
- user.unset_machine()
- return
- var/obj/machinery/camera/C = watchers[user]
- if(!C.can_use())
- user.unset_machine()
- return
-
-/obj/machinery/computer/security/on_unset_machine(mob/user)
- watchers.Remove(user)
- user.reset_perspective(null)
+ // Initialize map objects
+ cam_screen = new
+ cam_screen.name = "screen"
+ cam_screen.assigned_map = map_name
+ cam_screen.del_on_map_removal = FALSE
+ cam_screen.screen_loc = "[map_name]:1,1"
+ cam_plane_master = new
+ cam_plane_master.name = "plane_master"
+ cam_plane_master.assigned_map = map_name
+ cam_plane_master.del_on_map_removal = FALSE
+ cam_plane_master.screen_loc = "[map_name]:CENTER"
+ cam_background = new
+ cam_background.assigned_map = map_name
+ cam_background.del_on_map_removal = FALSE
/obj/machinery/computer/security/Destroy()
- if(watchers.len)
- for(var/mob/M in watchers)
- M.unset_machine() //to properly reset the view of the users if the console is deleted.
+ qdel(cam_screen)
+ qdel(cam_plane_master)
+ qdel(cam_background)
return ..()
-/obj/machinery/computer/security/can_interact(mob/user)
- if((!hasSiliconAccessInArea(user) && !Adjacent(user)) || is_blind(user) || !in_view_range(user, src))
- return FALSE
- return ..()
+/obj/machinery/computer/security/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+ for(var/i in network)
+ network -= i
+ network += "[idnum][i]"
-/obj/machinery/computer/security/interact(mob/user, special_state)
+/obj/machinery/computer/security/ui_interact(mob/user, datum/tgui/ui)
+ // Update UI
+ ui = SStgui.try_update_ui(user, src, ui)
+ // Show static if can't use the camera
+ if(!active_camera?.can_use())
+ show_camera_static()
+ if(!ui)
+ var/user_ref = REF(user)
+ var/is_living = isliving(user)
+ // Ghosts shouldn't count towards concurrent users, which produces
+ // an audible terminal_on click.
+ if(is_living)
+ concurrent_users += user_ref
+ // Turn on the console
+ if(length(concurrent_users) == 1 && is_living)
+ playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE)
+ use_power(active_power_usage)
+ // Register map objects
+ user.client.register_map_obj(cam_screen)
+ for(var/plane in cam_plane_master)
+ user.client.register_map_obj(plane)
+ user.client.register_map_obj(cam_background)
+ // Open UI
+ ui = new(user, src, "CameraConsole", name)
+ ui.open()
+
+/obj/machinery/computer/security/ui_data()
+ var/list/data = list()
+ data["network"] = network
+ data["activeCamera"] = null
+ if(active_camera)
+ data["activeCamera"] = list(
+ name = active_camera.c_tag,
+ status = active_camera.status,
+ )
+ return data
+
+/obj/machinery/computer/security/ui_static_data()
+ var/list/data = list()
+ data["mapRef"] = map_name
+ var/list/cameras = get_available_cameras()
+ data["cameras"] = list()
+ for(var/i in cameras)
+ var/obj/machinery/camera/C = cameras[i]
+ data["cameras"] += list(list(
+ name = C.c_tag,
+ ))
+ return data
+
+/obj/machinery/computer/security/ui_act(action, params)
. = ..()
- if (ismob(user) && !isliving(user)) // ghosts don't need cameras
- return
- if (!network)
- stack_trace("No camera network")
- user.unset_machine()
- return FALSE
- if (!(islist(network)))
- stack_trace("Camera network is not a list")
- user.unset_machine()
- return FALSE
-
- var/list/camera_list = get_available_cameras()
- if(!(user in watchers))
- for(var/Num in camera_list)
- var/obj/machinery/camera/CAM = camera_list[Num]
- if(istype(CAM) && CAM.can_use())
- watchers[user] = CAM //let's give the user the first usable camera, and then let him change to the camera he wants.
- break
- if(!(user in watchers))
- user.unset_machine() // no usable camera on the network, we disconnect the user from the computer.
- return FALSE
- playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0)
- use_camera_console(user)
-
-/obj/machinery/computer/security/proc/use_camera_console(mob/user)
- var/list/camera_list = get_available_cameras()
- var/t = input(user, "Which camera should you change to?") as null|anything in camera_list
- if(!src || user.machine != src) //while we were choosing we got disconnected from our computer or are using another machine.
- return
- if(!t || t == "Cancel")
- user.unset_machine()
- playsound(src, 'sound/machines/terminal_off.ogg', 25, 0)
+ if(.)
return
- var/obj/machinery/camera/C = camera_list[t]
+ if(action == "switch_camera")
+ var/c_tag = params["name"]
+ var/list/cameras = get_available_cameras()
+ var/obj/machinery/camera/C = cameras[c_tag]
+ active_camera = C
+ playsound(src, get_sfx("terminal_type"), 25, FALSE)
- if(!C || !C.can_use() || !can_interact(user))
- user.unset_machine()
- return FALSE
+ // Show static if can't use the camera
+ if(!active_camera?.can_use())
+ show_camera_static()
+ return TRUE
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
- if(isAI(user))
- var/mob/living/silicon/ai/A = user
- A.eyeobj.setLoc(get_turf(C))
- A.client.eye = A.eyeobj
- else
- user.reset_perspective(C)
- user.overlay_fullscreen("flash", /obj/screen/fullscreen/flash/static)
- user.clear_fullscreen("flash", 5)
- watchers[user] = C
- use_power(50)
- addtimer(CALLBACK(src, .proc/use_camera_console, user), 5)
+ var/list/visible_turfs = list()
+ for(var/turf/T in (C.isXRay() \
+ ? range(C.view_range, C) \
+ : view(C.view_range, C)))
+ visible_turfs += T
-//returns the list of cameras accessible from this computer
+ var/list/bbox = get_bbox_of_atoms(visible_turfs)
+ var/size_x = bbox[3] - bbox[1] + 1
+ var/size_y = bbox[4] - bbox[2] + 1
+
+ cam_screen.vis_contents = visible_turfs
+ cam_background.icon_state = "clear"
+ cam_background.fill_rect(1, 1, size_x, size_y)
+
+ return TRUE
+
+/obj/machinery/computer/security/ui_close(mob/user)
+ var/user_ref = REF(user)
+ var/is_living = isliving(user)
+ // Living creature or not, we remove you anyway.
+ concurrent_users -= user_ref
+ // Unregister map objects
+ user.client.clear_map(map_name)
+ // Turn off the console
+ if(length(concurrent_users) == 0 && is_living)
+ active_camera = null
+ playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE)
+ use_power(0)
+
+/obj/machinery/computer/security/proc/show_camera_static()
+ cam_screen.vis_contents.Cut()
+ cam_background.icon_state = "scanline2"
+ cam_background.fill_rect(1, 1, default_map_size, default_map_size)
+
+// Returns the list of cameras accessible from this computer
/obj/machinery/computer/security/proc/get_available_cameras()
var/list/L = list()
for (var/obj/machinery/camera/C in GLOB.cameranet.cameras)
if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras
continue
L.Add(C)
-
- camera_sort(L)
-
var/list/D = list()
- D["Cancel"] = "Cancel"
for(var/obj/machinery/camera/C in L)
if(!C.network)
stack_trace("Camera in a cameranet has no camera network")
@@ -114,9 +168,9 @@
if(!(islist(C.network)))
stack_trace("Camera in a cameranet has a non-list camera network")
continue
- var/list/tempnetwork = C.network&network
+ var/list/tempnetwork = C.network & network
if(tempnetwork.len)
- D["[C.c_tag][(C.status ? null : " (Deactivated)")]"] = C
+ D["[C.c_tag]"] = C
return D
// SECURITY MONITORS
@@ -127,7 +181,6 @@
icon_state = "television"
icon_keyboard = null
icon_screen = "detective_tv"
- clockwork = TRUE //it'd look weird
pass_flags = PASSTABLE
/obj/machinery/computer/security/mining
@@ -145,7 +198,7 @@
circuit = /obj/item/circuitboard/computer/research
/obj/machinery/computer/security/hos
- name = "Head of Security's camera console"
+ name = "\improper Head of Security's camera console"
desc = "A custom security console with added access to the labor camp network."
network = list("ss13", "labor")
circuit = null
@@ -157,7 +210,7 @@
circuit = null
/obj/machinery/computer/security/qm
- name = "Quartermaster's camera console"
+ name = "\improper Quartermaster's camera console"
desc = "A console with access to the mining, auxillary base and vault camera networks."
network = list("mine", "auxbase", "vault")
circuit = null
@@ -172,7 +225,6 @@
network = list("thunder")
density = FALSE
circuit = null
- clockwork = TRUE //it'd look very weird
light_power = 0
/obj/machinery/computer/security/telescreen/Initialize()
@@ -190,11 +242,35 @@
name = "entertainment monitor"
desc = "Damn, they better have the /tg/ channel on these things."
icon = 'icons/obj/status_display.dmi'
- icon_state = "entertainment"
+ icon_state = "entertainment_blank"
network = list("thunder")
+ density = FALSE
+ circuit = null
+ interaction_flags_atom = NONE // interact() is called by BigClick()
+ var/icon_state_off = "entertainment_blank"
+ var/icon_state_on = "entertainment"
+
+/obj/machinery/computer/security/telescreen/entertainment/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_CLICK, .proc/BigClick)
+
+// Bypass clickchain to allow humans to use the telescreen from a distance
+/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick()
+ interact(usr)
+
+/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on)
+ if(on && icon_state == icon_state_off)
+ say(pick(
+ "Feats of bravery live now at the thunderdome!",
+ "Two enter, one leaves! Tune in now!",
+ "Violence like you've never seen it before!",
+ "Spears! Camera! Action! LIVE NOW!"))
+ icon_state = icon_state_on
+ else
+ icon_state = icon_state_off
/obj/machinery/computer/security/telescreen/rd
- name = "Research Director's telescreen"
+ name = "\improper Research Director's telescreen"
desc = "Used for watching the AI and the RD's goons from the safety of his office."
network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test")
@@ -202,26 +278,26 @@
name = "circuitry telescreen"
desc = "Used for watching the other eggheads from the safety of the circuitry lab."
network = list("rd")
-
+
/obj/machinery/computer/security/telescreen/ce
- name = "Chief Engineer's telescreen"
+ name = "\improper Chief Engineer's telescreen"
desc = "Used for watching the engine, telecommunications and the minisat."
network = list("engine", "singularity", "tcomms", "minisat")
/obj/machinery/computer/security/telescreen/cmo
- name = "Chief Medical Officer's telescreen"
+ name = "\improper Chief Medical Officer's telescreen"
desc = "A telescreen with access to the medbay's camera network."
network = list("medbay")
/obj/machinery/computer/security/telescreen/vault
- name = "Vault monitor"
+ name = "vault monitor"
desc = "A telescreen that connects to the vault's camera network."
network = list("vault")
/obj/machinery/computer/security/telescreen/toxins
- name = "Bomb test site monitor"
+ name = "bomb test site monitor"
desc = "A telescreen that connects to the bomb test site's camera."
- network = list("toxin")
+ network = list("toxins")
/obj/machinery/computer/security/telescreen/engine
name = "engine monitor"
@@ -254,7 +330,7 @@
network = list("minisat")
/obj/machinery/computer/security/telescreen/aiupload
- name = "AI upload monitor"
+ name = "\improper AI upload monitor"
desc = "A telescreen that connects to the AI upload's camera network."
network = list("aiupload")
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index 5fe62ebb76..4b5806b8fd 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -29,9 +29,17 @@
if(lock_override & CAMERA_LOCK_REEBE)
z_lock |= SSmapping.levels_by_trait(ZTRAIT_REEBE)
+/obj/machinery/computer/camera_advanced/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+ for(var/i in networks)
+ networks -= i
+ networks += "[idnum][i]"
+
/obj/machinery/computer/camera_advanced/syndie
icon_keyboard = "syndie_key"
+/obj/machinery/computer/camera_advanced/syndie/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+ return //For syndie nuke shuttle, to spy for station.
+
/obj/machinery/computer/camera_advanced/proc/CreateEye()
eyeobj = new()
eyeobj.origin = src
@@ -95,10 +103,7 @@
return FALSE
return ..()
-/obj/machinery/computer/camera_advanced/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/machinery/computer/camera_advanced/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(current_user)
to_chat(user, "The console is already in use!")
return
@@ -268,112 +273,4 @@
C.overlay_fullscreen("flash", /obj/screen/fullscreen/flash/static)
C.clear_fullscreen("flash", 3) //Shorter flash than normal since it's an ~~advanced~~ console!
else
- playsound(origin, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
-
-
-//Used by servants of Ratvar! They let you beam to the station.
-/obj/machinery/computer/camera_advanced/ratvar
- name = "ratvarian camera observer"
- desc = "A console used to snoop on the station's goings-on. A jet of steam occasionally whooshes out from slats on its sides."
- use_power = FALSE
- networks = list("ss13", "minisat") //:eye:
- var/datum/action/innate/servant_warp/warp_action = new
-
-/obj/machinery/computer/camera_advanced/ratvar/Initialize()
- . = ..()
- ratvar_act()
-
-/obj/machinery/computer/camera_advanced/ratvar/process()
- if(prob(1))
- playsound(src, 'sound/machines/clockcult/steam_whoosh.ogg', 25, TRUE)
- new/obj/effect/temp_visual/steam_release(get_turf(src))
-
-/obj/machinery/computer/camera_advanced/ratvar/CreateEye()
- ..()
- eyeobj.visible_icon = TRUE
- eyeobj.icon = 'icons/mob/cameramob.dmi' //in case you still had any doubts
- eyeobj.icon_state = "generic_camera"
-
-/obj/machinery/computer/camera_advanced/ratvar/GrantActions(mob/living/carbon/user)
- ..()
- if(warp_action)
- warp_action.Grant(user)
- warp_action.target = src
- actions += warp_action
-
-/obj/machinery/computer/camera_advanced/ratvar/can_use(mob/living/user)
- if(!is_servant_of_ratvar(user))
- to_chat(user, "[src]'s keys are in a language foreign to you, and you don't understand anything on its screen.")
- return
- if(clockwork_ark_active())
- to_chat(user, "The Ark is active, and [src] has shut down.")
- return
- . = ..()
-
-/datum/action/innate/servant_warp
- name = "Warp"
- desc = "Warps to the tile you're viewing. You can use the Abscond scripture to return. Clicking this button again cancels the warp."
- icon_icon = 'icons/mob/actions/actions_clockcult.dmi'
- button_icon_state = "warp_down"
- background_icon_state = "bg_clock"
- buttontooltipstyle = "clockcult"
- var/cancel = FALSE //if TRUE, an active warp will be canceled
- var/obj/effect/temp_visual/ratvar/warp_marker/warping
-
-/datum/action/innate/servant_warp/Activate()
- if(QDELETED(target) || !(ishuman(owner) || iscyborg(owner)) || !owner.canUseTopic(target))
- return
- if(!GLOB.servants_active) //No leaving unless there's servants from the get-go
- return
- if(warping)
- cancel = TRUE
- return
- var/mob/living/carbon/human/user = owner
- var/mob/camera/aiEye/remote/remote_eye = user.remote_control
- var/obj/machinery/computer/camera_advanced/ratvar/R = target
- var/turf/T = get_turf(remote_eye)
- if(!is_reebe(user.z) || !is_station_level(T.z))
- return
- if(isclosedturf(T))
- to_chat(user, "You can't teleport into a wall.")
- return
- else if(isspaceturf(T))
- to_chat(user, "[prob(1) ? "Servant cannot into space." : "You can't teleport into space."]")
- return
- else if(T.flags_1 & NOJAUNT_1)
- to_chat(user, "This tile is blessed by holy water and deflects the warp.")
- return
- var/area/AR = get_area(T)
- if(!AR.clockwork_warp_allowed)
- to_chat(user, "[AR.clockwork_warp_fail]")
- return
- if(alert(user, "Are you sure you want to warp to [AR]?", target.name, "Warp", "Cancel") == "Cancel" || QDELETED(R) || !user.canUseTopic(R))
- return
- do_sparks(5, TRUE, user)
- do_sparks(5, TRUE, T)
- warping = new(T)
- user.visible_message("[user]'s [target.name] flares!", "You begin warping to [AR]...")
- button_icon_state = "warp_cancel"
- owner.update_action_buttons()
- if(!do_after(user, 50, target = warping, extra_checks = CALLBACK(src, .proc/is_canceled)))
- to_chat(user, "Warp interrupted.")
- QDEL_NULL(warping)
- button_icon_state = "warp_down"
- owner.update_action_buttons()
- cancel = FALSE
- return
- button_icon_state = "warp_down"
- owner.update_action_buttons()
- QDEL_NULL(warping)
- if(!do_teleport(user, T, channel = TELEPORT_CHANNEL_CULT, forced = TRUE))
- to_chat(user, "Warp Failed. Something deflected our attempt to warp to [AR].")
- return
- T.visible_message("[user] warps in!")
- playsound(user, 'sound/magic/magic_missile.ogg', 50, TRUE)
- playsound(T, 'sound/magic/magic_missile.ogg', 50, TRUE)
- user.setDir(SOUTH)
- flash_color(user, flash_color = "#AF0AAF", flash_time = 5)
- R.remove_eye_control(user)
-
-/datum/action/innate/servant_warp/proc/is_canceled()
- return !cancel
+ playsound(origin, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
\ No newline at end of file
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 22bea14381..6a99b248e3 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -61,7 +61,7 @@
// main interface
if("main")
state = STATE_DEFAULT
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
if("login")
var/mob/M = usr
@@ -73,19 +73,19 @@
auth_id = "[I.registered_name] ([I.assignment])"
if((ACCESS_CAPTAIN in I.access))
authenticated = 2
- playsound(src, 'sound/machines/terminal_on.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE)
if(obj_flags & EMAGGED)
authenticated = 2
auth_id = "Unknown"
to_chat(M, "[src] lets out a quiet alarm as its login is overridden.")
- playsound(src, 'sound/machines/terminal_on.ogg', 50, 0)
- playsound(src, 'sound/machines/terminal_alert.ogg', 25, 0)
+ playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE)
+ playsound(src, 'sound/machines/terminal_alert.ogg', 25, FALSE)
if(prob(25))
for(var/mob/living/silicon/ai/AI in active_ais())
SEND_SOUND(AI, sound('sound/machines/terminal_alert.ogg', volume = 10)) //Very quiet for balance reasons
if("logout")
authenticated = 0
- playsound(src, 'sound/machines/terminal_off.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE)
if("swipeidseclevel")
var/mob/M = usr
@@ -109,7 +109,7 @@
security_level_cd = world.time + 15 SECONDS
if(GLOB.security_level != old_level)
to_chat(usr, "Authorization confirmed. Modifying security level.")
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
//Only notify people if an actual change happened
var/security_level = NUM2SECLEVEL(GLOB.security_level)
log_game("[key_name(usr)] has changed the security level to [security_level] with [src] at [AREACOORD(usr)].")
@@ -118,28 +118,28 @@
tmp_alertlevel = 0
else
to_chat(usr, "You are not authorized to do this!")
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
tmp_alertlevel = 0
state = STATE_DEFAULT
else
to_chat(usr, "You need to swipe your ID!")
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
if("announce")
if(authenticated==2)
- playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
make_announcement(usr)
if("crossserver")
if(authenticated==2)
if(!checkCCcooldown())
- to_chat(usr, "Arrays recycling. Please stand by.")
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ to_chat(usr, "Arrays recycling. Please stand by.")
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
return
var/input = stripped_multiline_input(usr, "Please choose a message to transmit to allied stations. Please be aware that this process is very expensive, and abuse will lead to... termination.", "Send a message to an allied station.", "")
if(!input || !(usr in view(1,src)) || !checkCCcooldown())
return
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
send2otherserver("[station_name()]", input,"Comms_Console")
minor_announce(input, title = "Outgoing message to allied station")
usr.log_talk(input, LOG_SAY, tag="message to the other server")
@@ -168,22 +168,18 @@
if(D)
points_to_check = D.account_balance
if(points_to_check >= S.credit_cost)
- var/obj/machinery/shuttle_manipulator/M = locate() in GLOB.machines
- if(M)
- SSshuttle.shuttle_purchased = TRUE
- D.adjust_money(-S.credit_cost)
- minor_announce("[usr.real_name] has purchased [S.name] for [S.credit_cost] credits." , "Shuttle Purchase")
- message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [S.name].")
- SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]")
- M.unload_preview()
- M.load_template(S)
- M.existing_shuttle = SSshuttle.emergency
- M.action_load(S)
- message_admins("[S.name] loaded, purchased by [usr]")
- else
- to_chat(usr, "Something went wrong! The shuttle exchange system seems to be down.")
+ SSshuttle.shuttle_purchased = TRUE
+ SSshuttle.unload_preview()
+ SSshuttle.load_template(S)
+ SSshuttle.existing_shuttle = SSshuttle.emergency
+ SSshuttle.action_load(S)
+ D.adjust_money(-S.credit_cost)
+ minor_announce("[usr.real_name] has purchased [S.name] for [S.credit_cost] credits." , "Shuttle Purchase")
+ message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [S.name].")
+ log_shuttle("[key_name(usr)] has purchased [S.name].")
+ SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]")
else
- to_chat(usr, "Not enough credits.")
+ to_chat(usr, "Insufficient credits.")
if("callshuttle")
state = STATE_DEFAULT
@@ -268,7 +264,7 @@
// Status display stuff
if("setstat")
- playsound(src, "terminal_type", 50, 0)
+ playsound(src, "terminal_type", 50, FALSE)
switch(href_list["statdisp"])
if("message")
post_status("message", stat_msg1, stat_msg2)
@@ -308,13 +304,13 @@
if("MessageSyndicate")
if((authenticated==2) && (obj_flags & EMAGGED))
if(!checkCCcooldown())
- to_chat(usr, "Arrays recycling. Please stand by.")
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
+ to_chat(usr, "Arrays recycling. Please stand by.")
+ playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
return
var/input = stripped_input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING COORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to /??????/.", "")
if(!input || !(usr in view(1,src)) || !checkCCcooldown())
return
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
Syndicate_announce(input, usr)
to_chat(usr, "SYSERR @l(19833)of(transmit.dm): !@$ MESSAGE TRANSMITTED TO SYNDICATE COMMAND.")
for(var/client/X in GLOB.admins)
@@ -327,7 +323,7 @@
if("RestoreBackup")
to_chat(usr, "Backup routing data restored!")
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
obj_flags &= ~EMAGGED
updateDialog()
@@ -341,7 +337,7 @@
return
Nuke_request(input, usr)
to_chat(usr, "Request sent.")
- usr.log_message("has requested the nuclear codes from CentCom", LOG_SAY)
+ usr.log_message("has requested the nuclear codes from CentCom with reason \"[input]\"", LOG_SAY)
priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self Destruct Codes Requested","commandreport")
CM.lastTimeUsed = world.time
@@ -448,7 +444,7 @@
if(authenticated == 1)
authenticated = 2
to_chat(user, "You scramble the communication routing circuits!")
- playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE)
return TRUE
/obj/machinery/computer/communications/ui_interact(mob/user)
@@ -514,16 +510,16 @@
dat += " \[ Log In \]"
if(STATE_CALLSHUTTLE)
dat += get_call_shuttle_form()
- playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
if(STATE_CANCELSHUTTLE)
dat += get_cancel_shuttle_form()
- playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
if(STATE_MESSAGELIST)
dat += "Messages:"
for(var/i in 1 to messages.len)
var/datum/comm_message/M = messages[i]
dat += " [M.title]"
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
if(STATE_VIEWMESSAGE)
if (currmsg)
dat += "[currmsg.title]
[currmsg.content]"
@@ -557,7 +553,7 @@
dat += " Red Alert |"
dat += " Lockdown |"
dat += " Biohazard \] "
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
if(STATE_ALERT_LEVEL)
dat += "Current alert level: [NUM2SECLEVEL(GLOB.security_level)] "
if(GLOB.security_level == SEC_LEVEL_DELTA)
@@ -571,7 +567,7 @@
dat += "Confirm the change to: [NUM2SECLEVEL(tmp_alertlevel)] "
dat += "Swipe ID to confirm change. "
if(STATE_TOGGLE_EMERGENCY)
- playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
if(GLOB.emergency_access == 1)
dat += "Emergency Maintenance Access is currently ENABLED"
dat += " Restore maintenance access restrictions? \[ OK | Cancel \]"
@@ -722,7 +718,7 @@
/obj/machinery/computer/communications/proc/make_announcement(mob/living/user, is_silicon)
if(!SScommunications.can_announce(user, is_silicon))
- to_chat(user, "Intercomms recharging. Please stand by.")
+ to_chat(user, "Intercomms recharging. Please stand by.")
return
var/input = stripped_input(user, "Please choose a message to announce to the station crew.", "What?")
if(!input || !user.canUseTopic(src))
diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm
index 01a1d043a2..19da4f75d8 100644
--- a/code/game/machinery/computer/crew.dm
+++ b/code/game/machinery/computer/crew.dm
@@ -76,11 +76,10 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
/datum/crewmonitor/Destroy()
return ..()
-/datum/crewmonitor/ui_interact(mob/user, ui_key = "crew", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/crewmonitor/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
- ui = new(user, src, ui_key, "crew", "crew monitor", 800, 600 , master_ui, state)
+ ui = new(user, src, "CrewConsole")
ui.open()
/datum/crewmonitor/proc/show(mob/M, source)
diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm
index d465ff2022..286c106788 100644
--- a/code/game/machinery/computer/dna_console.dm
+++ b/code/game/machinery/computer/dna_console.dm
@@ -1,25 +1,41 @@
+/// Base timeout for creating mutation activators and other injectors
#define INJECTOR_TIMEOUT 100
+/// Maximum number of genetic makeup storage slots in DNA Console
#define NUMBER_OF_BUFFERS 3
+/// Timeout for DNA Scramble in DNA Consoles
#define SCRAMBLE_TIMEOUT 600
-#define JOKER_TIMEOUT 12000 //20 minutes
-#define JOKER_UPGRADE 1800
+/// Timeout for using the Joker feature to solve a gene in DNA Console
+#define JOKER_TIMEOUT 12000
+/// How much time DNA Scanner upgrade tiers remove from JOKER_TIMEOUT
+#define JOKER_UPGRADE 3000
+/// Maximum value for radiaton strength when pulsing enzymes
#define RADIATION_STRENGTH_MAX 15
-#define RADIATION_STRENGTH_MULTIPLIER 1 //larger has more range
+/// Larger multipliers will affect the range of values when pulsing enzymes
+#define RADIATION_STRENGTH_MULTIPLIER 1
+/// Maximum value for the radiation pulse duration when pulsing enzymes
#define RADIATION_DURATION_MAX 30
-#define RADIATION_ACCURACY_MULTIPLIER 3 //larger is less accurate
+/// Large values reduce pulse accuracy and may pulse other enzymes than selected
+#define RADIATION_ACCURACY_MULTIPLIER 3
+/// Special status indicating a scanner occupant is transforming eg. from monkey to human
+#define STATUS_TRANSFORMING 4
-#define RADIATION_IRRADIATION_MULTIPLIER 1 //multiplier for how much radiation a test subject receives
+/// Multiplier for how much radiation received from DNA Console functionality
+#define RADIATION_IRRADIATION_MULTIPLIER 1
-#define SCANNER_ACTION_SE 1
-#define SCANNER_ACTION_UI 2
-#define SCANNER_ACTION_UE 3
-#define SCANNER_ACTION_MIXED 4
+/// Flag for the mutation ref search system. Search will include scanner occupant
+#define SEARCH_OCCUPANT 1
+/// Flag for the mutation ref search system. Search will include console storage
+#define SEARCH_STORED 2
+/// Flag for the mutation ref search system. Search will include diskette storage
+#define SEARCH_DISKETTE 4
+/// Flag for the mutation ref search system. Search will include advanced injector mutations
+#define SEARCH_ADV_INJ 8
/obj/machinery/computer/scan_consolenew
- name = "\improper DNA scanner access console"
+ name = "DNA Console"
desc = "Scan DNA."
icon_screen = "dna"
icon_keyboard = "med_key"
@@ -31,47 +47,103 @@
active_power_usage = 400
light_color = LIGHT_COLOR_BLUE
+ /// Link to the techweb's stored research. Used to retrieve stored mutations
var/datum/techweb/stored_research
+ /// Maximum number of mutations that DNA Consoles are able to store
var/max_storage = 6
- var/combine
+ /// Duration for enzyme radiation pulses
var/radduration = 2
+ /// Strength for enzyme radiation pulses
var/radstrength = 1
+ /// Maximum number of chromosomes that DNA Consoles are able to store.
var/max_chromosomes = 6
-
- ///Amount of mutations we can store
- var/list/buffer[NUMBER_OF_BUFFERS]
- ///mutations we have stored
+ /// Maximum number of enzymes we can store
+ var/list/genetic_makeup_buffer[NUMBER_OF_BUFFERS]
+ /// List of all mutations stored on the DNA Console
var/list/stored_mutations = list()
- ///chromosomes we have stored
+ /// List of all chromosomes stored in the DNA Console
var/list/stored_chromosomes = list()
- ///combinations of injectors for the 'injector selection'. format is list("Elsa" = list(Cryokinesis, Geladikinesis), "The Hulk" = list(Hulk, Gigantism), etc) Glowy and the gang being an initialized datum
- var/list/injector_selection = list()
- ///max amount of selections you can make
+ /// Assoc list of all advanced injectors. Keys are injector names. Values are lists of mutations.
+ var/list/list/injector_selection = list()
+ /// Maximum number of advanced injectors that DNA Consoles store
var/max_injector_selections = 2
- ///hard-cap on the advanced dna injector
+ /// Maximum number of mutation that an advanced injector can store
var/max_injector_mutations = 10
- ///the max instability of the advanced injector.
+ /// Maximum total instability of all combined mutations allowed on an advanced injector
var/max_injector_instability = 50
- var/injectorready = 0 //world timer cooldown var
+ /// World time when injectors are ready to be printed
+ var/injectorready = 0
+ /// World time when JOKER algorithm can be used in DNA Consoles
var/jokerready = 0
+ /// World time when Scramble can be used in DNA Consoles
var/scrambleready = 0
- var/current_screen = "mainmenu"
- var/current_mutation //what block are we inspecting? only used when screen = "info"
- var/current_storage //what storage block are we looking at?
- var/obj/machinery/dna_scannernew/connected = null
+
+ /// Currently stored genetic data diskette
var/obj/item/disk/data/diskette = null
+
+ /// Current delayed action, used for delayed enzyme transfer on scanner door close
var/list/delayed_action = null
+ /// Index of the enzyme being modified during delayed enzyme pulse operations
+ var/rad_pulse_index = 0
+ /// World time when the enzyme pulse should complete
+ var/rad_pulse_timer = 0
+
+ /// Used for setting tgui data - Whether the connected DNA Scanner is usable
+ var/can_use_scanner = FALSE
+ /// Used for setting tgui data - Whether the current DNA Scanner occupant is viable for genetic modification
+ var/is_viable_occupant = FALSE
+ /// Used for setting tgui data - Whether Scramble DNA is ready
+ var/is_scramble_ready = FALSE
+ /// Used for setting tgui data - Whether JOKER algorithm is ready
+ var/is_joker_ready = FALSE
+ /// Used for setting tgui data - Whether injectors are ready to be printed
+ var/is_injector_ready = FALSE
+ /// Used for setting tgui data - Wheher an enzyme pulse operation is ongoing
+ var/is_pulsing_rads = FALSE
+ /// Used for setting tgui data - Time until scramble is ready
+ var/time_to_scramble = 0
+ /// Used for setting tgui data - Time until joker is ready
+ var/time_to_joker = 0
+ /// Used for setting tgui data - Time until injectors are ready
+ var/time_to_injector = 0
+ /// Used for setting tgui data - Time until the enzyme pulse is complete
+ var/time_to_pulse = 0
+
+ /// Currently connected DNA Scanner
+ var/obj/machinery/dna_scannernew/connected_scanner = null
+ /// Current DNA Scanner occupant
+ var/mob/living/carbon/scanner_occupant = null
+
+ /// Used for setting tgui data - List of occupant mutations
+ var/list/tgui_occupant_mutations = list()
+ /// Used for setting tgui data - List of DNA Console stored mutations
+ var/list/tgui_console_mutations = list()
+ /// Used for setting tgui data - List of diskette stored mutations
+ var/list/tgui_diskette_mutations = list()
+ /// Used for setting tgui data - List of DNA Console chromosomes
+ var/list/tgui_console_chromosomes = list()
+ /// Used for setting tgui data - List of occupant mutations
+ var/list/tgui_genetic_makeup = list()
+ /// Used for setting tgui data - List of occupant mutations
+ var/list/tgui_advinjector_mutations = list()
+
+
+ /// State of tgui view, i.e. which tab is currently active, or which genome we're currently looking at.
+ var/list/list/tgui_view_state = list()
+
+/obj/machinery/computer/scan_consolenew/process()
+ . = ..()
+
+ // This is for pulsing the UI element with radiation as part of genetic makeup
+ // If rad_pulse_index > 0 then it means we're attempting a rad pulse
+ if((rad_pulse_index > 0) && (rad_pulse_timer <= world.time))
+ rad_pulse()
+ return
+
/obj/machinery/computer/scan_consolenew/attackby(obj/item/I, mob/user, params)
- if (istype(I, /obj/item/disk/data)) //INSERT SOME DISKETTES
- if (!src.diskette)
- if (!user.transferItemToLoc(I,src))
- return
- src.diskette = I
- to_chat(user, "You insert [I].")
- src.updateUsrDialog()
- return
+ // Store chromosomes in the console if there's room
if (istype(I, /obj/item/chromosome))
if(LAZYLEN(stored_chromosomes) < max_chromosomes)
I.forceMove(src)
@@ -80,856 +152,1794 @@
else
to_chat(user, "You cannot store any more chromosomes!")
return
+
+ // Insert data disk if console disk slot is empty
+ // Swap data disk if there is one already a disk in the console
+ if (istype(I, /obj/item/disk/data)) //INSERT SOME DISKETTES
+ // Insert disk into DNA Console
+ if (!user.transferItemToLoc(I,src))
+ return
+ // If insertion was successful and there's already a diskette in the console, eject the old one.
+ if(diskette)
+ eject_disk(user)
+ // Set the new diskette.
+ diskette = I
+ to_chat(user, "You insert [I].")
+ return
+
+ // Recycle non-activator used injectors
+ // Turn activator used injectors (aka research injectors) to chromosomes
if(istype(I, /obj/item/dnainjector/activator))
var/obj/item/dnainjector/activator/A = I
if(A.used)
to_chat(user,"Recycled [I].")
if(A.research)
- var/c_typepath = generate_chromosome()
- var/obj/item/chromosome/CM = new c_typepath (drop_location())
- to_chat(user,"Recycled [I].")
- if((LAZYLEN(stored_chromosomes) < max_chromosomes) && prob(60))
- CM.forceMove(src)
- stored_chromosomes += CM
- to_chat(user,"[capitalize(CM.name)] added to storage.")
+ if(prob(60))
+ var/c_typepath = generate_chromosome()
+ var/obj/item/chromosome/CM = new c_typepath (drop_location())
+ if(LAZYLEN(stored_chromosomes) < max_chromosomes)
+ CM.forceMove(src)
+ stored_chromosomes += CM
+ to_chat(user,"[capitalize(CM.name)] added to storage.")
+ else
+ to_chat(user, "You cannot store any more chromosomes!")
+ to_chat(user, "[capitalize(CM.name)] added on top of the console.")
+ else
+ to_chat(user, "There was not enough genetic data to extract a viable chromosome.")
qdel(I)
return
- else
- return ..()
+
+ return ..()
+
+
+/obj/machinery/computer/scan_consolenew/AltClick(mob/user)
+ // Make sure the user can interact with the machine.
+ if(!user.canUseTopic(src, !issilicon(user)))
+ return
+
+ eject_disk(user)
/obj/machinery/computer/scan_consolenew/Initialize()
. = ..()
- for(var/direction in GLOB.cardinals)
- connected = locate(/obj/machinery/dna_scannernew, get_step(src, direction))
- if(!isnull(connected))
- break
+
+ // Connect with a nearby DNA Scanner on init
+ connect_to_scanner()
+
+ // Set appropriate ready timers and limits for machines functions
injectorready = world.time + INJECTOR_TIMEOUT
scrambleready = world.time + SCRAMBLE_TIMEOUT
jokerready = world.time + JOKER_TIMEOUT
+ // Set the default tgui state
+ set_default_state()
+
+ // Link machine with research techweb. Used for discovering and accessing
+ // already discovered mutations
stored_research = SSresearch.science_tech
/obj/machinery/computer/scan_consolenew/examine(mob/user)
. = ..()
- if(jokerready < world.time)
- . += "JOKER algorithm available."
+
+/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, datum/tgui/ui)
+ // Most of ui_interact is spent setting variables for passing to the tgui
+ // interface.
+ // We can also do some general state processing here too as it's a good
+ // indication that a player is using the console.
+
+ var/scanner_op = scanner_operational()
+ var/can_modify_occ = can_modify_occupant()
+
+ // Check for connected AND operational scanner.
+ if(scanner_op)
+ can_use_scanner = TRUE
else
- . += "JOKER algorithm available in about [round(0.00166666667 * (jokerready - world.time))] minutes."
+ can_use_scanner = FALSE
+ connected_scanner = null
+ is_viable_occupant = FALSE
-/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, last_change)
- . = ..()
- if(!user)
- return
- var/datum/browser/popup = new(user, "scannernew", "DNA Modifier Console", 800, 630) // Set up the popup browser window
- if(user.client)
- var/datum/asset/simple/assets = get_asset_datum(/datum/asset/simple/genetics)
- assets.send(user.client)
- if(!(in_range(src, user) || hasSiliconAccessInArea(user)))
- popup.close()
- return
- popup.add_stylesheet("scannernew", 'html/browser/scannernew.css')
+ // Check for a viable occupant in the scanner.
+ if(can_modify_occ)
+ is_viable_occupant = TRUE
+ else
+ is_viable_occupant = FALSE
- var/mob/living/carbon/viable_occupant
- var/list/occupant_status = list("
"
- return temp_html
-
-/obj/machinery/computer/scan_consolenew/Topic(href, href_list)
+/obj/machinery/computer/scan_consolenew/ui_act(action, var/list/params)
if(..())
- return
- if(!isturf(usr.loc))
- return
- if(!((isturf(loc) && in_range(src, usr)) || hasSiliconAccessInArea(usr)))
- return
- if(current_screen == "working")
- return
+ return TRUE
+
+ . = TRUE
add_fingerprint(usr)
usr.set_machine(src)
- var/mob/living/carbon/viable_occupant = get_viable_occupant()
+ switch(action)
+ // Connect this DNA Console to a nearby DNA Scanner
+ // Usually only activate as an option if there is no connected scanner
+ if("connect_scanner")
+ connect_to_scanner()
+ return
- //Basic Tasks///////////////////////////////////////////
- var/num = round(text2num(href_list["num"]))
- var/last_change
- switch(href_list["task"])
- if("togglelock")
- if(connected)
- connected.locked = !connected.locked
- if("toggleopen")
- if(connected)
- connected.toggle_open(usr)
- if("setduration")
- if(!num)
- num = round(input(usr, "Choose pulse duration:", "Input an Integer", null) as num|null)
- if(num)
- radduration = WRAP(num, 1, RADIATION_DURATION_MAX+1)
- if("setstrength")
- if(!num)
- num = round(input(usr, "Choose pulse strength:", "Input an Integer", null) as num|null)
- if(num)
- radstrength = WRAP(num, 1, RADIATION_STRENGTH_MAX+1)
- if("screen")
- current_screen = href_list["text"]
- if("scramble")
- if(viable_occupant && (scrambleready < world.time))
- viable_occupant.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA))
- viable_occupant.dna.generate_dna_blocks()
- scrambleready = world.time + SCRAMBLE_TIMEOUT
- to_chat(usr,"DNA scrambled.")
- viable_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER*50/(connected.damage_coeff ** 2)
- if("setbufferlabel")
- var/text = sanitize(input(usr, "Input a new label:", "Input a Text", null) as text|null)
- if(num && text)
- num = clamp(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- buffer_slot["label"] = text
- if("setbuffer")
- if(num && viable_occupant)
- num = clamp(num, 1, NUMBER_OF_BUFFERS)
- buffer[num] = list(
- "label"="Buffer[num]:[viable_occupant.real_name]",
- "UI"=viable_occupant.dna.uni_identity,
- "UE"=viable_occupant.dna.unique_enzymes,
- "name"=viable_occupant.real_name,
- "blood_type"=viable_occupant.dna.blood_type
- )
- if("clearbuffer")
- if(num)
- num = clamp(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- buffer_slot.Cut()
- if("transferbuffer")
- if(num && viable_occupant)
- switch(href_list["text"]) //Numbers are this high because other way upgrading laser is just not worth the hassle, and i cant think of anything better to inmrove
- if("ui")
- apply_buffer(SCANNER_ACTION_UI,num)
- if("ue")
- apply_buffer(SCANNER_ACTION_UE,num)
- if("mixed")
- apply_buffer(SCANNER_ACTION_MIXED,num)
- if("injector")
- if(num && injectorready < world.time)
- num = clamp(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- var/obj/item/dnainjector/timed/I
- switch(href_list["text"])
- if("ui")
- if(buffer_slot["UI"])
- I = new /obj/item/dnainjector/timed(loc)
- I.fields = list("UI"=buffer_slot["UI"])
- if(connected)
- I.damage_coeff = connected.damage_coeff
- if("ue")
- if(buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- I = new /obj/item/dnainjector/timed(loc)
- I.fields = list("name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
- if(connected)
- I.damage_coeff = connected.damage_coeff
- if("mixed")
- if(buffer_slot["UI"] && buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- I = new /obj/item/dnainjector/timed(loc)
- I.fields = list("UI"=buffer_slot["UI"],"name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
- if(connected)
- I.damage_coeff = connected.damage_coeff
- if(I)
- injectorready = world.time + INJECTOR_TIMEOUT
- if("loaddisk")
- if(num && diskette && diskette.fields)
- num = clamp(num, 1, NUMBER_OF_BUFFERS)
- buffer[num] = diskette.fields.Copy()
- if("savedisk")
- if(num && diskette && !diskette.read_only)
- num = clamp(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- diskette.name = "data disk \[[buffer_slot["label"]]\]"
- diskette.fields = buffer_slot.Copy()
- if("ejectdisk")
- if(diskette)
- diskette.forceMove(drop_location())
- diskette = null
- if("setdelayed")
- if(num)
- delayed_action = list("action"=text2num(href_list["delayaction"]),"buffer"=num)
- if("pulseui")
- if(num && viable_occupant && connected)
- radduration = WRAP(radduration, 1, RADIATION_DURATION_MAX+1)
- radstrength = WRAP(radstrength, 1, RADIATION_STRENGTH_MAX+1)
+ // Toggle the door open/closed status on attached DNA Scanner
+ if("toggle_door")
+ // GUARD CHECK - Scanner still connected and operational?
+ if(!scanner_operational())
+ return
- var/locked_state = connected.locked
- connected.locked = TRUE
+ connected_scanner.toggle_open(usr)
+ return
- current_screen = "working"
- ui_interact(usr)
+ // Toggle the door bolts on the attached DNA Scanner
+ if("toggle_lock")
+ // GUARD CHECK - Scanner still connected and operational?
+ if(!scanner_operational())
+ return
- sleep(radduration*10)
- current_screen = "ui"
+ connected_scanner.locked = !connected_scanner.locked
+ return
- if(viable_occupant && connected && connected.occupant==viable_occupant)
- viable_occupant.radiation += (RADIATION_IRRADIATION_MULTIPLIER*radduration*radstrength)/(connected.damage_coeff ** 2) //Read comment in "transferbuffer" section above for explanation
- switch(href_list["task"]) //Same thing as there but values are even lower, on best part they are about 0.0*, effectively no damage
- if("pulseui")
- var/len = length_char(viable_occupant.dna.uni_identity)
- num = WRAP(num, 1, len+1)
- num = randomize_radiation_accuracy(num, radduration + (connected.precision_coeff ** 2), len) //Each manipulator level above 1 makes randomization as accurate as selected time + manipulator lvl^2
- //Value is this high for the same reason as with laser - not worth the hassle of upgrading if the bonus is low
- var/block = round((num-1)/DNA_BLOCK_SIZE)+1
- var/subblock = num - block*DNA_BLOCK_SIZE
- last_change = "UI #[block]-[subblock]; "
+ // Scramble scanner occupant's DNA
+ if("scramble_dna")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ // GUARD CHECK - Is scramble DNA actually ready?
+ if(!can_modify_occupant() || !(scrambleready < world.time))
+ return
- var/hex = copytext_char(viable_occupant.dna.uni_identity, num, num+1)
- last_change += "[hex]"
- hex = scramble(hex, radstrength, radduration)
- last_change += "->[hex]"
+ scanner_occupant.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA))
+ scanner_occupant.dna.generate_dna_blocks()
+ scrambleready = world.time + SCRAMBLE_TIMEOUT
+ to_chat(usr,"DNA scrambled.")
+ scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER*50/(connected_scanner.damage_coeff ** 2)
+ return
- viable_occupant.dna.uni_identity = copytext_char(viable_occupant.dna.uni_identity, 1, num) + hex + copytext_char(viable_occupant.dna.uni_identity, num + 1)
- viable_occupant.updateappearance(mutations_overlay_update=1)
+ // Check whether a specific mutation is eligible for discovery within the
+ // scanner occupant
+ // This is additionally done when a mutation's tab is selected in the tgui
+ // interface. This is because some mutations, such as Monkified on monkeys,
+ // are infact completed by default but not yet discovered. Likewise, all
+ // mutations can have their sequence completed while Monkified is still an
+ // active mutation and thus won't immediately be discovered but could be
+ // discovered when Monkified is removed
+ // ---------------------------------------------------------------------- //
+ // params["alias"] - Alias of a mutation. The alias is the "hidden" name of
+ // the mutation, for example "Mutation 5" or "Mutation 33"
+ if("check_discovery")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // GUARD CHECK - Have we somehow cheekily swapped occupants? This is
+ // unexpected.
+ if(!(scanner_occupant == connected_scanner.occupant))
+ return
+
+ check_discovery(params["alias"])
+ return
+
+ // Check all mutations of the occupant and check if any are discovered.
+ // This is called when the Genetic Sequencer is selected. It'll do things
+ // like immediately discover Monkified without needing to click through
+ // the mutation tabs and handle cases where mutations are solved but not
+ // discovered due to the Monkified mutation being active then removed.
+ if("all_check_discovery")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // GUARD CHECK - Have we somehow cheekily swapped occupants? This is
+ // unexpected.
+ if(!(scanner_occupant == connected_scanner.occupant))
+ return
+
+ // Go over all standard mutations and check if they've been discovered.
+ for(var/mutation_type in scanner_occupant.dna.mutation_index)
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type)
+ check_discovery(HM.alias)
+
+ return
+
+ // Set a gene in a mutation's genetic sequence. Will also check for mutations
+ // discovery as part of the process.
+ // ---------------------------------------------------------------------- //
+ // params["alias"] - Alias of a mutation. The alias is the "hidden" name of
+ // the mutation, for example "Mutation 5" or "Mutation 33"
+ // params["gene"] - The letter of the new gene
+ // params["pos"] - The BYOND index of the letter in the gene sequence to be
+ // changed. Expects a text string from TGUI and will convert to a number
+ if("pulse_gene")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // GUARD CHECK - Have we somehow cheekily swapped occupants? This is
+ // unexpected.
+ if(!(scanner_occupant == connected_scanner.occupant))
+ return
+
+ // GUARD CHECK - Is the occupant currently undergoing some form of
+ // transformation? If so, we don't want to be pulsing genes.
+ if(scanner_occupant.transformation_timer)
+ to_chat(usr,"Gene pulse failed: The scanner occupant undergoing a transformation.")
+ return
+
+ // Resolve mutation's BYOND path from the alias
+ var/alias = params["alias"]
+ var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias)
+ // Make sure the occupant still has this mutation
+ if(!(path in scanner_occupant.dna.mutation_index))
+ return
+
+ // Resolve BYOND path to genome sequence of scanner occupant
+ var/sequence = GET_GENE_STRING(path, scanner_occupant.dna)
+
+ var/newgene = params["gene"]
+ var/genepos = text2num(params["pos"])
+
+ // If the new gene is J, this means we're dealing with a JOKER
+ // GUARD CHECK - Is JOKER actually ready?
+ if((newgene == "J") && (jokerready < world.time))
+ var/truegenes = GET_SEQUENCE(path)
+ newgene = truegenes[genepos]
+ jokerready = world.time + JOKER_TIMEOUT - (JOKER_UPGRADE * (connected_scanner.precision_coeff-1))
+
+ // If the gene is an X, we want to update the default genes with the new
+ // X to allow highlighting logic to work on the tgui interface.
+ if(newgene == "X")
+ var/defaultseq = scanner_occupant.dna.default_mutation_genes[path]
+ defaultseq = copytext_char(defaultseq, 1, genepos) + newgene + copytext_char(defaultseq, genepos + 1)
+ scanner_occupant.dna.default_mutation_genes[path] = defaultseq
+
+ // Copy genome to scanner occupant and do some basic mutation checks as
+ // we've increased the occupant rads
+ sequence = copytext_char(sequence, 1, genepos) + newgene + copytext_char(sequence, genepos + 1)
+ scanner_occupant.dna.mutation_index[path] = sequence
+ scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER/connected_scanner.damage_coeff
+ scanner_occupant.domutcheck()
+
+ // GUARD CHECK - Modifying genetics can lead to edge cases where the
+ // scanner occupant is qdel'd and replaced with a different entity.
+ // Examples of this include adding/removing the Monkified mutation which
+ // qdels the previous entity and creates a brand new one in its place.
+ // We should redo all of our occupant modification checks again, although
+ // it is less than ideal.
+ if(!can_modify_occupant())
+ return
+
+ // Check if we cracked a mutation
+ check_discovery(alias)
+
+ return
+
+ // Apply a chromosome to a specific mutation.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to apply the chromo to
+ // params["chromo"] - Name of the chromosome to apply to the mutation
+ if("apply_chromo")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // GUARD CHECK - Have we somehow cheekily swapped occupants? This is
+ // unexpected.
+ if(!(scanner_occupant == connected_scanner.occupant))
+ return
+
+ var/bref = params["mutref"]
+
+ // GUARD CHECK - Only search occupant for this specific ref, since your
+ // can only apply chromosomes to mutations occupants.
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // Look through our stored chromos and compare names to find a
+ // stored chromo we can apply.
+ for(var/obj/item/chromosome/CM in stored_chromosomes)
+ if(CM.can_apply(HM) && (CM.name == params["chromo"]))
+ stored_chromosomes -= CM
+ CM.apply(HM)
+
+ return
+
+ // Print any type of standard injector, limited right now to activators that
+ // activate a dormant mutation and mutators that forcibly create a new
+ // MUT_EXTRA mutation
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to create an injector of
+ // params["is_activator"] - Is this an "Activator" style injector, also
+ // referred to as a "Research" type. Expects a string with 0 or 1, which
+ // then gets converted to a number.
+ // params["source"] - The source the request came from.
+ // Expected results:
+ // "occupant" - From genetic sequencer
+ // "console" - From DNA Console storage
+ // "disk" - From inserted diskette
+ if("print_injector")
+ // Because printing mutators and activators share a bunch of code,
+ // it makes sense to keep them both together and set unique vars
+ // later in the code
+
+ // As a side note, because mutations can contain unique metadata,
+ // this system uses BYOND Atom Refs to safely and accurately
+ // identify mutations from big ol' lists
+
+ // GUARD CHECK - Is the injector actually ready?
+ if(world.time < injectorready)
+ return
+
+ var/search_flags = 0
+
+ switch(params["source"])
+ if("occupant")
+ // GUARD CHECK - Make sure we can modify the occupant before we
+ // attempt to search them for any given mutation refs. This could
+ // lead to no search flags being passed to get_mut_by_ref and this
+ // is intended functionality to prevent any cheese or abuse
+ if(can_modify_occupant())
+ search_flags |= SEARCH_OCCUPANT
+ if("console")
+ search_flags |= SEARCH_STORED
+ if("disk")
+ search_flags |= SEARCH_DISKETTE
+
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // Create a new DNA Injector and add the appropriate mutations to it
+ var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
+ I.add_mutations += new HM.type(copymut = HM)
+
+ var/is_activator = text2num(params["is_activator"])
+
+ // Activators are also called "research" injectors and are used to create
+ // chromosomes by recycling at the DNA Console
+ if(is_activator)
+ I.name = "[HM.name] activator"
+ I.research = TRUE
+ // If there's an operational connected scanner, we can use its upgrades
+ // to improve our injector's radiation generation
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff*4
+ injectorready = world.time + INJECTOR_TIMEOUT * (1 - 0.1 * connected_scanner.precision_coeff)
else
- current_screen = "mainmenu"
-
- if(connected)
- connected.locked = locked_state
- if("inspect")
- if(viable_occupant)
- var/list/mutations = get_mutation_list(TRUE)
- if(current_mutation == mutations[num])
- current_mutation = null
+ injectorready = world.time + INJECTOR_TIMEOUT
+ else
+ I.name = "[HM.name] mutator"
+ I.doitanyway = TRUE
+ // If there's an operational connected scanner, we can use its upgrades
+ // to improve our injector's radiation generation
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+ injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - 0.1 * connected_scanner.precision_coeff)
else
- current_mutation = mutations[num]
+ injectorready = world.time + INJECTOR_TIMEOUT * 5
+
+ return
+
+ // Save a mutation to the console's storage buffer.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to store
+ // params["source"] - The source the request came from.
+ // Expected results:
+ // "occupant" - From genetic sequencer
+ // "disk" - From inserted diskette
+ if("save_console")
+ var/search_flags = 0
+
+ switch(params["source"])
+ if("occupant")
+ // GUARD CHECK - Make sure we can modify the occupant before we
+ // attempt to search them for any given mutation refs. This could
+ // lead to no search flags being passed to get_mut_by_ref and this
+ // is intended functionality to prevent any cheese or abuse
+ if(can_modify_occupant())
+ search_flags |= SEARCH_OCCUPANT
+ if("disk")
+ search_flags |= SEARCH_DISKETTE
+
+ // GUARD CHECK - Is mutation storage full?
+ if(LAZYLEN(stored_mutations) >= max_storage)
+ to_chat(usr,"Mutation storage is full.")
+ return
+
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ var/datum/mutation/human/A = new HM.type()
+ A.copy_mutation(HM)
+ stored_mutations += A
+ to_chat(usr,"Mutation successfully stored.")
+ return
+
+ // Save a mutation to the diskette's storage buffer.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to store
+ // params["source"] - The source the request came from
+ // Expected results:
+ // "occupant" - From genetic sequencer
+ // "console" - From DNA Console storage
+ if("save_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - Make sure the disk is not full
+ if(LAZYLEN(diskette.mutations) >= diskette.max_mutations)
+ to_chat(usr,"Disk storage is full.")
+ return
+
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write to it
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+
+ var/search_flags = 0
+
+ switch(params["source"])
+ if("occupant")
+ // GUARD CHECK - Make sure we can modify the occupant before we
+ // attempt to search them for any given mutation refs. This could
+ // lead to no search flags being passed to get_mut_by_ref and this
+ // is intended functionality to prevent any cheese or abuse
+ if(can_modify_occupant())
+ search_flags |= SEARCH_OCCUPANT
+ if("console")
+ search_flags |= SEARCH_STORED
+
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ var/datum/mutation/human/A = new HM.type()
+ A.copy_mutation(HM)
+ diskette.mutations += A
+ to_chat(usr,"Mutation successfully stored to disk.")
+ return
+
+ // Completely removes a MUT_EXTRA mutation or mutation with corrupt gene
+ // sequence from the scanner occupant
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to nullify
+ if("nullify")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // GUARD CHECK - Nullify should only be used on scrambled or "extra"
+ // mutations.
+ if(!HM.scrambled && !(HM.class == MUT_EXTRA))
+ return
+
+ scanner_occupant.dna.remove_mutation(HM.type)
+ return
+
+ // Deletes saved mutation from console buffer.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to delete
+ if("delete_console_mut")
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_STORED)
- if("inspectstorage")
- current_storage = num
- current_screen = "info"
- if("savemut")
- if(viable_occupant)
- var/succes
- if(LAZYLEN(stored_mutations) < max_storage)
- var/mutation = text2path(href_list["path"])
- if(ispath(mutation, /datum/mutation/human)) //sanity checks
- var/datum/mutation/human/HM = viable_occupant.dna.get_mutation(mutation)
- if(HM)
- var/datum/mutation/human/A = new HM.type()
- A.copy_mutation(HM)
- succes = TRUE
- stored_mutations += A
- to_chat(usr,"Mutation succesfully stored.")
- if(!succes) //we can exactly return here
- to_chat(usr,"Mutation storage is full.")
- if("deletemut")
- var/datum/mutation/human/HM = stored_mutations[num]
if(HM)
stored_mutations.Remove(HM)
qdel(HM)
- current_screen = "mutations"
- if("activator")
- if(injectorready < world.time)
- var/mutation = text2path(href_list["path"])
- if(ispath(mutation, /datum/mutation/human))
- var/datum/mutation/human/HM = get_valid_mutation(mutation)
- if(HM)
- var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
- I.add_mutations += new HM.type (copymut = HM)
- I.name = "[HM.name] activator"
- I.research = TRUE
- if(connected)
- I.damage_coeff = connected.damage_coeff*4
- injectorready = world.time + INJECTOR_TIMEOUT * (1 - 0.1 * connected.precision_coeff) //precision_coeff being the matter bin rating
- else
- injectorready = world.time + INJECTOR_TIMEOUT
- if("mutator")
- if(injectorready < world.time)
- var/mutation = text2path(href_list["path"])
- if(ispath(mutation, /datum/mutation/human))
- var/datum/mutation/human/HM = get_valid_mutation(mutation)
- if(HM)
- var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
- I.add_mutations += new HM.type (copymut = HM)
- I.doitanyway = TRUE
- I.name = "[HM.name] injector"
- if(connected)
- I.damage_coeff = connected.damage_coeff
- injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - 0.1 * connected.precision_coeff)
- else
- injectorready = world.time + INJECTOR_TIMEOUT * 5
- if("advinjector")
- var/selection = href_list["injector"]
- if(injectorready < world.time)
- if(injector_selection.Find(selection))
- var/list/true_selection = injector_selection[selection]
- if(LAZYLEN(injector_selection))
- var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
- for(var/A in true_selection)
- var/datum/mutation/human/HM = A
- I.add_mutations += new HM.type (copymut = HM)
- I.doitanyway = TRUE
- I.name = "Advanced [selection] injector"
- if(connected)
- I.damage_coeff = connected.damage_coeff
- injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - 0.1 * connected.precision_coeff)
- else
- injectorready = world.time + INJECTOR_TIMEOUT * 8
- if("nullify")
- if(viable_occupant)
- var/datum/mutation/human/A = viable_occupant.dna.get_mutation(current_mutation)
- if(A && (!viable_occupant.dna.mutation_in_sequence(current_mutation) || A.scrambled))
- viable_occupant.dna.remove_mutation(current_mutation)
- current_screen = "mainmenu"
- current_mutation = null
- if("pulsegene")
- if(current_screen != "info")
- var/path = text2path(href_list["path"])
- if(viable_occupant && num && (path in viable_occupant.dna.mutation_index))
- var/list/genes = list("A","T","G","C","X")
- if(jokerready < world.time)
- genes += "JOKER"
- var/sequence = GET_GENE_STRING(path, viable_occupant.dna)
- var/original = sequence[num]
- var/new_gene = input("From [original] to-", "New block", original) as null|anything in genes
- if(!new_gene)
- new_gene = original
- if(viable_occupant == get_viable_occupant()) //No cheesing
- if((new_gene == "JOKER") && (jokerready < world.time))
- var/true_genes = GET_SEQUENCE(current_mutation)
- new_gene = true_genes[num]
- jokerready = world.time + JOKER_TIMEOUT - (JOKER_UPGRADE * (connected.precision_coeff-1))
- sequence = copytext(sequence, 1, num) + new_gene + copytext(sequence, num+1, length(sequence)+1)
- viable_occupant.dna.mutation_index[path] = sequence
- viable_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER/connected.damage_coeff
- viable_occupant.domutcheck()
- if("exportdiskmut")
- if(diskette && !diskette.read_only)
- var/path = text2path(href_list["path"])
- if(ispath(path, /datum/mutation/human))
- var/datum/mutation/human/A = get_valid_mutation(path)
- if(A && diskette && (LAZYLEN(diskette.mutations) < diskette.max_mutations))
- var/datum/mutation/human/HM = new A.type()
- diskette.mutations += HM
- HM.copy_mutation(A)
- to_chat(usr, "Succesfully written [A.name] to [diskette.name].")
- if("deletediskmut")
- if(diskette && !diskette.read_only)
- if(num && (LAZYLEN(diskette.mutations) >= num))
- var/datum/mutation/human/A = diskette.mutations[num]
- diskette.mutations.Remove(A)
- qdel(A)
- if("importdiskmut")
- if(diskette && (LAZYLEN(diskette.mutations) >= num))
- if(LAZYLEN(stored_mutations) < max_storage)
- var/datum/mutation/human/A = diskette.mutations[num]
- var/datum/mutation/human/HM = new A.type()
- HM.copy_mutation(A)
- stored_mutations += HM
- to_chat(usr,"Succesfully written [A.name] to storage.")
- if("combine")
- if(num && (LAZYLEN(stored_mutations) >= num))
- if(LAZYLEN(stored_mutations) < max_storage)
- var/datum/mutation/human/A = stored_mutations[num]
- var/path = A.type
- if(combine)
- var/result_path = get_mixed_mutation(combine, path)
- if(result_path)
- stored_mutations += new result_path()
- to_chat(usr, "Succes! New mutation has been added to storage")
- discover(result_path)
- combine = null
- else
- to_chat(usr, "Failed. No mutation could be created.")
- combine = null
- else
- combine = path
- to_chat(usr,"Selected [A.name] for combining")
- else
- to_chat(usr, "Not enough space to store potential mutation.")
- if("ejectchromosome")
- if(LAZYLEN(stored_chromosomes) <= num)
- var/obj/item/chromosome/CM = stored_chromosomes[num]
- CM.forceMove(drop_location())
- adjust_item_drop_location(CM)
- stored_chromosomes -= CM
- if("applychromosome")
- if(viable_occupant && (LAZYLEN(viable_occupant.dna.mutations) <= num))
- var/datum/mutation/human/HM = viable_occupant.dna.mutations[num]
- var/list/chromosomes = list()
- for(var/obj/item/chromosome/CM in stored_chromosomes)
- if(CM.can_apply(HM))
- chromosomes += CM
- if(chromosomes.len)
- var/obj/item/chromosome/CM = input("Select a chromosome to apply", "Apply Chromosome") as null|anything in sortNames(chromosomes)
- if(CM)
- to_chat(usr, "You apply [CM] to [HM.name].")
- stored_chromosomes -= CM
- CM.apply(HM)
- if("expand_advinjector")
- var/mutation = text2path(href_list["path"])
- var/datum/mutation/human/HM = get_valid_mutation(mutation)
- if(HM && LAZYLEN(injector_selection))
- var/which_injector = input(usr, "Select Adv. Injector", "Advanced Injectors") as null|anything in injector_selection
- if(injector_selection.Find(which_injector))
- var/list/true_selection = injector_selection[which_injector]
- var/total_instability
- for(var/B in true_selection)
- var/datum/mutation/human/mootacion = B
- total_instability += mootacion.instability
- total_instability += HM.instability
- if((total_instability > max_injector_instability) || (true_selection.len + 1) > max_injector_mutations)
- to_chat(usr, "Adding more mutations would make the advanced injector too unstable!")
- else
- true_selection += HM //reminder that this works. because I keep forgetting this works
- if("remove_from_advinjector")
- var/mutation = text2path(href_list["path"])
- var/selection = href_list["injector"]
- if(injector_selection.Find(selection))
- var/list/true_selection = injector_selection[selection]
- for(var/B in true_selection)
- var/datum/mutation/human/HM = B
- if(HM.type == mutation)
- true_selection -= HM
- break
+ return
- if("remove_advinjector")
- var/selection = href_list["injector"]
- for(selection in injector_selection)
- if(selection == selection)
- injector_selection.Remove(selection)
+ // Deletes saved mutation from disk buffer.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to delete
+ if("delete_disk_mut")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
- if("add_advinjector")
- if(LAZYLEN(injector_selection) < max_injector_selections)
- var/new_selection = input(usr, "Enter Adv. Injector name", "Advanced Injectors") as text|null
- if(new_selection && !(new_selection in injector_selection))
- injector_selection[new_selection] = list()
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write to it (via deletion)
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_DISKETTE)
- ui_interact(usr,last_change)
+ if(HM)
+ diskette.mutations.Remove(HM)
+ qdel(HM)
-/obj/machinery/computer/scan_consolenew/proc/scramble(input,rs,rd) //hexadecimal genetics. dont confuse with scramble button
+ return
+
+ // Ejects a stored chromosome from the DNA Console
+ // ---------------------------------------------------------------------- //
+ // params["chromo"] - Text string of the chromosome name
+ if("eject_chromo")
+ var/chromname = params["chromo"]
+
+ for(var/obj/item/chromosome/CM in stored_chromosomes)
+ if(chromname == CM.name)
+ CM.forceMove(drop_location())
+ adjust_item_drop_location(CM)
+ stored_chromosomes -= CM
+ return
+
+ return
+
+ // Combines two mutations from the console to try and create a new mutation
+ // ---------------------------------------------------------------------- //
+ // params["firstref"] - ATOM Ref of first mutation for combination
+ // params["secondref"] - ATOM Ref of second mutation for combination
+ // mutation
+ if("combine_console")
+ // GUaRD CHECK - Make sure mutation storage isn't full. If it is, we won't
+ // be able to store the new combo mutation
+ if(LAZYLEN(stored_mutations) >= max_storage)
+ to_chat(usr,"Mutation storage is full.")
+ return
+
+ // GUARD CHECK - We're running a research-type operation. If, for some
+ // reason, somehow the DNA Console has been disconnected from the research
+ // network - Or was never in it to begin with - don't proceed
+ if(!stored_research)
+ return
+
+ var/first_bref = params["firstref"]
+ var/second_bref = params["secondref"]
+
+ // GUARD CHECK - Find the source and destination mutations on the console
+ // and make sure they actually exist.
+ var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE)
+ if(!source_mut)
+ return
+
+ var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE)
+ if(!dest_mut)
+ return
+
+ // Attempt to mix the two mutations to get a new type
+ var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type)
+
+ if(!result_path)
+ return
+
+ // If we got a new type, add it to our storage
+ stored_mutations += new result_path()
+ to_chat(usr, "Success! New mutation has been added to console storage.")
+
+ // If it's already discovered, end here. Otherwise, add it to the list of
+ // discovered mutations.
+ // We've already checked for stored_research earlier
+ if(result_path in stored_research.discovered_mutations)
+ return
+
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path)
+ stored_research.discovered_mutations += result_path
+ say("Successfully mutated [HM.name].")
+ return
+
+ // Combines two mutations from the disk to try and create a new mutation
+ // ---------------------------------------------------------------------- //
+ // params["firstref"] - ATOM Ref of first mutation for combination
+ // params["secondref"] - ATOM Ref of second mutation for combination
+ // mutation
+ if("combine_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - Make sure the disk is not full.
+ if(LAZYLEN(diskette.mutations) >= diskette.max_mutations)
+ to_chat(usr,"Disk storage is full.")
+ return
+
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write to it
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+
+ // GUARD CHECK - We're running a research-type operation. If, for some
+ // reason, somehow the DNA Console has been disconnected from the research
+ // network - Or was never in it to begin with - don't proceed
+ if(!stored_research)
+ return
+
+ var/first_bref = params["firstref"]
+ var/second_bref = params["secondref"]
+
+ // GUARD CHECK - Find the source and destination mutations on the console
+ // and make sure they actually exist.
+ var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE)
+ if(!source_mut)
+ return
+
+ var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE)
+ if(!dest_mut)
+ return
+
+ // Attempt to mix the two mutations to get a new type
+ var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type)
+
+ if(!result_path)
+ return
+
+ // If we got a new type, add it to our storage
+ diskette.mutations += new result_path()
+ to_chat(usr, "Success! New mutation has been added to the disk.")
+
+ // If it's already discovered, end here. Otherwise, add it to the list of
+ // discovered mutations
+ // We've already checked for stored_research earlier
+ if(result_path in stored_research.discovered_mutations)
+ return
+
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path)
+ stored_research.discovered_mutations += result_path
+ say("Successfully mutated [HM.name].")
+ return
+
+ // Sets the Genetic Makeup pulse strength.
+ // ---------------------------------------------------------------------- //
+ // params["val"] - New strength value as text string, converted to number
+ // later on in code
+ if("set_pulse_strength")
+ var/value = round(text2num(params["val"]))
+ radstrength = WRAP(value, 1, RADIATION_STRENGTH_MAX+1)
+ return
+
+ // Sets the Genetic Makeup pulse duration
+ // ---------------------------------------------------------------------- //
+ // params["val"] - New strength value as text string, converted to number
+ // later on in code
+ if("set_pulse_duration")
+ var/value = round(text2num(params["val"]))
+ radduration = WRAP(value, 1, RADIATION_DURATION_MAX+1)
+ return
+
+ // Saves Genetic Makeup information to disk
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // copy to disk
+ if("save_makeup_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write to it
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+
+ // Convert the index to a number and clamp within the array range
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This should not be possible to activate on a buffer slot
+ // that doesn't have any genetic data. Unexpected result
+ if(!istype(buffer_slot))
+ return
+
+ diskette.genetic_makeup_buffer = buffer_slot.Copy()
+ return
+
+ // Loads Genetic Makeup from disk to a console buffer
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // copy to. Expected as text string, converted to number later
+ if("load_makeup_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - This should not be possible to activate on a diskette
+ // that doesn't have any genetic data. Unexpected result
+ if(LAZYLEN(diskette.genetic_makeup_buffer) == 0)
+ return
+
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ genetic_makeup_buffer[buffer_index] = diskette.genetic_makeup_buffer.Copy()
+ return
+
+ // Deletes genetic makeup buffer from the inserted diskette
+ if("del_makeup_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write (via deletion) to it
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+
+ diskette.genetic_makeup_buffer.Cut()
+ return
+
+ // Saves the scanner occupant's genetic makeup to a given console buffer
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // save the new genetic data to. Expected as text string, converted to
+ // number later
+ if("save_makeup_console")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+
+ // Set the new information
+ genetic_makeup_buffer[buffer_index] = list(
+ "label"="Slot [buffer_index]:[scanner_occupant.real_name]",
+ "UI"=scanner_occupant.dna.uni_identity,
+ "UE"=scanner_occupant.dna.unique_enzymes,
+ "name"=scanner_occupant.real_name,
+ "blood_type"=scanner_occupant.dna.blood_type)
+
+ return
+
+ // Deleted genetic makeup data from a console buffer slot
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // delete the genetic data from. Expected as text string, converted to
+ // number later
+ if("del_makeup_console")
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This shouldn't be possible to execute this on a null
+ // buffer. Unexpected resut
+ if(!istype(buffer_slot))
+ return
+
+ genetic_makeup_buffer[buffer_index] = null
+ return
+
+ // Eject stored diskette from console
+ if("eject_disk")
+ eject_disk(usr)
+ return
+
+ // Create a Genetic Makeup injector. These injectors are timed and thus are
+ // only temporary
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // create the makeup injector from. Expected as text string, converted to
+ // number later
+ // params["type"] - Type of injector to create
+ // Expected results:
+ // "ue" - Unique Enzyme, changes name and blood type
+ // "ui" - Unique Identity, changes looks
+ // "mixed" - Combination of both ue and ui
+ if("makeup_injector")
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This shouldn't be possible to execute this on a null
+ // buffer. Unexpected resut
+ if(!istype(buffer_slot))
+ return
+
+ var/type = params["type"]
+ var/obj/item/dnainjector/timed/I
+
+ switch(type)
+ if("ui")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["UI"])
+ to_chat(usr,"Genetic data corrupted, unable to create injector.")
+ return
+
+ I = new /obj/item/dnainjector/timed(loc)
+ I.fields = list("UI"=buffer_slot["UI"])
+
+ // If there is a connected scanner, we can use its upgrades to reduce
+ // the radiation generated by this injector
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+ if("ue")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
+ to_chat(usr,"Genetic data corrupted, unable to create injector.")
+ return
+
+ I = new /obj/item/dnainjector/timed(loc)
+ I.fields = list("name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
+
+ // If there is a connected scanner, we can use its upgrades to reduce
+ // the radiation generated by this injector
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+ if("mixed")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
+ to_chat(usr,"Genetic data corrupted, unable to create injector.")
+ return
+
+ I = new /obj/item/dnainjector/timed(loc)
+ I.fields = list("UI"=buffer_slot["UI"],"name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
+
+ // If there is a connected scanner, we can use its upgrades to reduce
+ // the radiation generated by this injector
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+
+ // If we successfully created an injector, don't forget to set the new
+ // ready timer.
+ if(I)
+ injectorready = world.time + INJECTOR_TIMEOUT
+
+ return
+
+ // Applies a genetic makeup buffer to the scanner occupant
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // apply to the scanner occupant. Expected as text string, converted to
+ // number later
+ // params["type"] - Type of genetic makeup copy to implement
+ // Expected results:
+ // "ue" - Unique Enzyme, changes name and blood type
+ // "ui" - Unique Identity, changes looks
+ // "mixed" - Combination of both ue and ui
+ if("makeup_apply")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This shouldn't be possible to execute this on a null
+ // buffer. Unexpected resut
+ if(!istype(buffer_slot))
+ return
+
+ var/type = params["type"]
+
+ apply_genetic_makeup(type, buffer_slot)
+ return
+
+ // Applies a genetic makeup buffer to the next scanner occupant. This sets
+ // some code that will run when the connected DNA Scanner door is next
+ // closed
+ // This allows people to self-modify their genetic makeup, as tgui
+ // interfaces can not be accessed while inside the DNA Scanner and genetic
+ // makeup injectors are only temporary
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // apply to the scanner occupant. Expected as text string, converted to
+ // number later
+ // params["type"] - Type of genetic makeup copy to implement
+ // Expected results:
+ // "ue" - Unique Enzyme, changes name and blood type
+ // "ui" - Unique Identity, changes looks
+ // "mixed" - Combination of both ue and ui
+ if("makeup_delay")
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This shouldn't be possible to execute this on a null
+ // buffer. Unexpected resut
+ if(!istype(buffer_slot))
+ return
+
+ var/type = params["type"]
+
+ // Set the delayed action. The next time the scanner door is closed,
+ // unless this is cancelled in the UI, the action will happen
+ delayed_action = list("type" = type, "buffer_slot" = buffer_slot)
+ return
+
+ // Attempts to modify the indexed element of the Unique Identity string
+ // This is a time delayed action that is handled in process()
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the Unique Identity string to
+ // attempt to modify
+ if("makeup_pulse")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // Set the appropriate timer and index to pulse. This is then managed
+ // later on in process()
+ var/len = length_char(scanner_occupant.dna.uni_identity)
+ rad_pulse_timer = world.time + (radduration*10)
+ rad_pulse_index = WRAP(text2num(params["index"]), 1, len+1)
+ START_PROCESSING(SSobj, src)
+ return
+
+ // Cancels the delayed action - In this context it is not the radiation
+ // pulse from "makeup_pulse", which can not be cancelled. It is instead
+ // the delayed genetic transfer from "makeup_delay"
+ if("cancel_delay")
+ delayed_action = null
+ return
+
+ // Creates a new advanced injector storage buffer in the console
+ // ---------------------------------------------------------------------- //
+ // params["name"] - The name to apply to the new injector
+ if("new_adv_inj")
+ // GUARD CHECK - Make sure we can make a new injector. This code should
+ // not be called if we're already maxed out and this is an Unexpected
+ // result
+ if(!(LAZYLEN(injector_selection) < max_injector_selections))
+ return
+
+ // GUARD CHECK - Sanitise and trim the proposed name. This prevents HTML
+ // injection and equivalent as tgui input is not stripped
+ var/inj_name = params["name"]
+ inj_name = trim(sanitize(inj_name))
+
+ // GUARD CHECK - If the name is null or blank, or the name is already in
+ // the list of advanced injectors, we want to reject it as we can't have
+ // duplicate named advanced injectors
+ if(!inj_name || (inj_name in injector_selection))
+ return
+
+ injector_selection[inj_name] = list()
+ return
+
+ // Deleted an advanced injector storage buffer from the console
+ // ---------------------------------------------------------------------- //
+ // params["name"] - The name of the injector to delete
+ if("del_adv_inj")
+ var/inj_name = params["name"]
+
+ // GUARD CHECK - If the name is null or blank, reject.
+ // GUARD CHECK - If the name isn't in the list of advanced injectors, we
+ // want to reject this as it shouldn't be possible ever do this.
+ // Unexpected result
+ if(!inj_name || !(inj_name in injector_selection))
+ return
+
+ injector_selection.Remove(inj_name)
+ return
+
+ // Creates an injector from an advanced injector buffer
+ // ---------------------------------------------------------------------- //
+ // params["name"] - The name of the injector to print
+ if("print_adv_inj")
+ // As a side note, because mutations can contain unique metadata,
+ // this system uses BYOND Atom Refs to safely and accurately
+ // identify mutations from big ol' lists.
+
+ // GUARD CHECK - Is the injector actually ready?
+ if(world.time < injectorready)
+ return
+
+ var/inj_name = params["name"]
+
+ // GUARD CHECK - If the name is null or blank, reject.
+ // GUARD CHECK - If the name isn't in the list of advanced injectors, we
+ // want to reject this as it shouldn't be possible ever do this.
+ // Unexpected result
+ if(!inj_name || !(inj_name in injector_selection))
+ return
+
+ var/list/injector = injector_selection[inj_name]
+ var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
+
+ // Run through each mutation in our Advanced Injector and add them to a
+ // new injector
+ for(var/A in injector)
+ var/datum/mutation/human/HM = A
+ I.add_mutations += new HM.type(copymut=HM)
+
+ // Force apply any mutations, this is functionality similar to mutators
+ I.doitanyway = TRUE
+ I.name = "Advanced [inj_name] injector"
+
+ // If there's an operational connected scanner, we can use its upgrades
+ // to improve our injector's radiation generation
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+ injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - 0.1 * connected_scanner.precision_coeff)
+ else
+ injectorready = world.time + INJECTOR_TIMEOUT * 8
+
+ return
+
+ // Adds a mutation to an advanced injector
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to add to the injector
+ // params["advinj"] - Name of the advanced injector to add the mutation to
+ if("add_advinj_mut")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ // This is needed because this operation can only be completed from the
+ // genetic sequencer.
+ if(!can_modify_occupant())
+ return
+
+ var/adv_inj = params["advinj"]
+
+ // GUARD CHECK - Make sure our advanced injector actually exists. This
+ // should not be possible. Unexpected result
+ if(!(adv_inj in injector_selection))
+ return
+
+ // GUARD CHECK - Make sure we limit the number of mutations appropriately
+ if(LAZYLEN(injector_selection[adv_inj]) >= max_injector_mutations)
+ to_chat(usr,"Advanced injector mutation storage is full.")
+ return
+
+ var/mut_source = params["source"]
+ var/search_flag = 0
+
+ switch(mut_source)
+ if("disk")
+ search_flag = SEARCH_DISKETTE
+ if("occupant")
+ search_flag = SEARCH_OCCUPANT
+ if("console")
+ search_flag = SEARCH_STORED
+
+ if(!search_flag)
+ return
+
+ var/bref = params["mutref"]
+ // We've already made sure we can modify the occupant, so this is safe to
+ // call
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flag)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // We want to make sure we stick within the instability limit.
+ // We start with the instability of the mutation we're intending to add.
+ var/instability_total = HM.instability
+
+ // We then add the instabilities of all other mutations in the injector,
+ // remembering to apply the Stabilizer chromosome modifiers
+ for(var/datum/mutation/human/I in injector_selection[adv_inj])
+ instability_total += I.instability * GET_MUTATION_STABILIZER(I)
+
+ // If this would take us over the max instability, we inform the user.
+ if(instability_total > max_injector_instability)
+ to_chat(usr,"Extra mutation would make the advanced injector too instable.")
+ return
+
+ // If we've got here, all our checks are passed and we can successfully
+ // add the mutation to the advanced injector.
+ var/datum/mutation/human/A = new HM.type()
+ A.copy_mutation(HM)
+ injector_selection[adv_inj] += A
+ to_chat(usr,"Mutation successfully added to advanced injector.")
+ return
+
+ // Deletes a mutation from an advanced injector
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to del from the injector
+ if("delete_injector_mut")
+ var/bref = params["mutref"]
+
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_ADV_INJ)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // Check Advanced Injectors to find and remove the mutation
+ for(var/I in injector_selection)
+ if(injector_selection["[I]"].Remove(HM))
+ qdel(HM)
+ return
+
+ return
+
+ // Sets a new tgui view state
+ // ---------------------------------------------------------------------- //
+ // params["id"] - Key for the state to set
+ // params[...] - Every other element is used to set state variables
+ if("set_view")
+ for (var/key in params)
+ if(key == "src")
+ continue
+ tgui_view_state[key] = params[key]
+ return TRUE
+ return FALSE
+
+/**
+ * Applies the enzyme buffer to the current scanner occupant
+ *
+ * Applies the type of a specific genetic makeup buffer to the current scanner
+ * occupant
+ *
+ * Arguments:
+ * * type - "ui"/"ue"/"mixed" - Which part of the enzyme buffer to apply
+ * * buffer_slot - Index of the enzyme buffer to apply
+ */
+/obj/machinery/computer/scan_consolenew/proc/apply_genetic_makeup(type, buffer_slot)
+ // Note - This proc is only called from code that has already performed the
+ // necessary occupant guard checks. If you call this code yourself, please
+ // apply can_modify_occupant() or equivalent checks first.
+
+ // Pre-calc the rad increase since we'll be using it in all the possible
+ // operations
+ var/rad_increase = rand(100/(connected_scanner.damage_coeff ** 2),250/(connected_scanner.damage_coeff ** 2))
+
+ switch(type)
+ if("ui")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["UI"])
+ to_chat(usr,"Genetic data corrupted, unable to apply genetic data.")
+ return FALSE
+ scanner_occupant.dna.uni_identity = buffer_slot["UI"]
+ scanner_occupant.updateappearance(mutations_overlay_update=1)
+ scanner_occupant.radiation += rad_increase
+ scanner_occupant.domutcheck()
+ return TRUE
+ if("ue")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
+ to_chat(usr,"Genetic data corrupted, unable to apply genetic data.")
+ return FALSE
+ scanner_occupant.real_name = buffer_slot["name"]
+ scanner_occupant.name = buffer_slot["name"]
+ scanner_occupant.dna.unique_enzymes = buffer_slot["UE"]
+ scanner_occupant.dna.blood_type = buffer_slot["blood_type"]
+ scanner_occupant.radiation += rad_increase
+ scanner_occupant.domutcheck()
+ return TRUE
+ if("mixed")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
+ to_chat(usr,"Genetic data corrupted, unable to apply genetic data.")
+ return FALSE
+ scanner_occupant.dna.uni_identity = buffer_slot["UI"]
+ scanner_occupant.updateappearance(mutations_overlay_update=1)
+ scanner_occupant.real_name = buffer_slot["name"]
+ scanner_occupant.name = buffer_slot["name"]
+ scanner_occupant.dna.unique_enzymes = buffer_slot["UE"]
+ scanner_occupant.dna.blood_type = buffer_slot["blood_type"]
+ scanner_occupant.radiation += rad_increase
+ scanner_occupant.domutcheck()
+ return TRUE
+
+ return FALSE
+/**
+ * Checks if there is a connected DNA Scanner that is operational
+ */
+/obj/machinery/computer/scan_consolenew/proc/scanner_operational()
+ if(!connected_scanner)
+ return FALSE
+
+ return (connected_scanner && connected_scanner.is_operational())
+
+/**
+ * Checks if there is a valid DNA Scanner occupant for genetic modification
+ *
+ * Checks if there is a valid subject in the DNA Scanner that can be genetically
+ * modified. Will set the scanner occupant var as part of this check.
+ * Requires that the scanner can be operated and will return early if it can't
+ */
+/obj/machinery/computer/scan_consolenew/proc/can_modify_occupant()
+ // GUARD CHECK - We always want to perform the scanner operational check as
+ // part of checking if we can modify the occupant.
+ // We can never modify the occupant of a broken scanner.
+ if(!scanner_operational())
+ return FALSE
+
+ if(!connected_scanner.occupant)
+ return FALSE
+
+ scanner_occupant = connected_scanner.occupant
+
+ // Check validity of occupent for DNA Modification
+ // DNA Modification:
+ // requires DNA
+ // this DNA can not be bad
+ // is done via radiation bursts, so radiation immune carbons are not viable
+ // And the DNA Scanner itself must have a valid scan level
+ if(scanner_occupant.has_dna() && !HAS_TRAIT(scanner_occupant, TRAIT_RADIMMUNE) && !HAS_TRAIT(scanner_occupant, TRAIT_NOCLONE) || (connected_scanner.scan_level == 3))
+ return TRUE
+
+ return FALSE
+
+/**
+ * Checks for adjacent DNA scanners and connects when it finds a viable one
+ *
+ * Seearches cardinal directions in order. Stops when it finds a viable DNA Scanner.
+ * Will connect to a broken scanner if no functional scanner is available.
+ * Links itself to the DNA Scanner to receive door open and close events.
+ */
+/obj/machinery/computer/scan_consolenew/proc/connect_to_scanner()
+ var/obj/machinery/dna_scannernew/test_scanner = null
+ var/obj/machinery/dna_scannernew/broken_scanner = null
+
+ // Look in each cardinal direction and try and find a DNA Scanner
+ // If you find a DNA Scanner, check to see if it broken or working
+ // If it's working, set the current scanner and return early
+ // If it's not working, remember it anyway as a broken scanner
+ for(var/direction in GLOB.cardinals)
+ test_scanner = locate(/obj/machinery/dna_scannernew, get_step(src, direction))
+ if(!isnull(test_scanner))
+ if(test_scanner.is_operational())
+ connected_scanner = test_scanner
+ connected_scanner.linked_console = src
+ return
+ else
+ broken_scanner = test_scanner
+
+ // Ultimately, if we have a broken scanner, we'll attempt to connect to it as
+ // a fallback case, but the code above will prefer a working scanner
+ if(!isnull(broken_scanner))
+ connected_scanner = broken_scanner
+ connected_scanner.linked_console = src
+
+/**
+ * Called by connected DNA Scanners when their doors close.
+ *
+ * Sets the new scanner occupant and completes delayed enzyme transfer if one
+ * is queued.
+ */
+/obj/machinery/computer/scan_consolenew/proc/on_scanner_close()
+ // Set the appropriate occupant now the scanner is closed
+ if(connected_scanner.occupant)
+ scanner_occupant = connected_scanner.occupant
+ else
+ scanner_occupant = null
+
+ // If we have a delayed action - In this case the only delayed action is
+ // applying a genetic makeup buffer the next time the DNA Scanner is closed -
+ // we want to perform it.
+ // GUARD CHECK - Make sure we can modify the occupant, apply_genetic_makeup()
+ // assumes we've already done this.
+ if(delayed_action && can_modify_occupant())
+ var/type = delayed_action["type"]
+ var/buffer_slot = delayed_action["buffer_slot"]
+ if(apply_genetic_makeup(type, buffer_slot))
+ to_chat(connected_scanner.occupant, "[src] activates!")
+ delayed_action = null
+
+/**
+ * Called by connected DNA Scanners when their doors open.
+ *
+ * Clears enzyme pulse operations, stops processing and nulls the current
+ * scanner occupant var.
+ */
+/obj/machinery/computer/scan_consolenew/proc/on_scanner_open()
+ // If we had a radiation pulse action ongoing, we want to stop this.
+ // Imagine it being like a microwave stopping when you open the door.
+ rad_pulse_index = 0
+ rad_pulse_timer = 0
+ STOP_PROCESSING(SSobj, src)
+ scanner_occupant = null
+
+/**
+ * Builds the genetic makeup list which will be sent to tgui interface.
+ */
+/obj/machinery/computer/scan_consolenew/proc/build_genetic_makeup_list()
+ // No code will ever null this list, we can safely Cut it.
+ tgui_genetic_makeup.Cut()
+
+ for(var/i=1, i <= NUMBER_OF_BUFFERS, i++)
+ if(genetic_makeup_buffer[i])
+ tgui_genetic_makeup["[i]"] = genetic_makeup_buffer[i].Copy()
+ else
+ tgui_genetic_makeup["[i]"] = null
+
+/**
+ * Builds the genetic makeup list which will be sent to tgui interface.
+ *
+ * Will iterate over the connected scanner occupant, DNA Console, inserted
+ * diskette and chromosomes and any advanced injectors, building the main data
+ * structures which get passed to the tgui interface.
+ */
+/obj/machinery/computer/scan_consolenew/proc/build_mutation_list(can_modify_occ)
+ // No code will ever null these lists. We can safely Cut them.
+ tgui_occupant_mutations.Cut()
+ tgui_diskette_mutations.Cut()
+ tgui_console_mutations.Cut()
+ tgui_console_chromosomes.Cut()
+ tgui_advinjector_mutations.Cut()
+
+ // ------------------------------------------------------------------------ //
+ // GUARD CHECK - Can we genetically modify the occupant? This check will have
+ // previously included checks to make sure the DNA Scanner is still
+ // operational
+ if(can_modify_occ)
+ // ---------------------------------------------------------------------- //
+ // Start cataloguing all mutations that the occupant has by default
+ for(var/mutation_type in scanner_occupant.dna.mutation_index)
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type)
+
+ var/list/mutation_data = list()
+ var/text_sequence = scanner_occupant.dna.mutation_index[mutation_type]
+ var/default_sequence = scanner_occupant.dna.default_mutation_genes[mutation_type]
+ var/discovered = (stored_research && (mutation_type in stored_research.discovered_mutations))
+
+ mutation_data["Alias"] = HM.alias
+ mutation_data["Sequence"] = text_sequence
+ mutation_data["DefaultSeq"] = default_sequence
+ mutation_data["Discovered"] = discovered
+ mutation_data["Source"] = "occupant"
+
+ // We only want to pass this information along to the tgui interface if
+ // the mutation has been discovered. Prevents people being able to cheese
+ // or "hack" their way to figuring out what undiscovered mutations are
+ if(discovered)
+ mutation_data["Name"] = HM.name
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+ mutation_data["Quality"] = HM.quality
+
+ // Assume the mutation is normal unless assigned otherwise.
+ var/mut_class = MUT_NORMAL
+
+ // Check if the mutation is currently activated. If it is, we can add even
+ // MORE information to send to tgui.
+ var/datum/mutation/human/A = scanner_occupant.dna.get_mutation(mutation_type)
+ if(A)
+ mutation_data["Active"] = TRUE
+ mutation_data["Scrambled"] = A.scrambled
+ mutation_data["Class"] = A.class
+ mut_class = A.class
+ mutation_data["CanChromo"] = A.can_chromosome
+ mutation_data["ByondRef"] = REF(A)
+ mutation_data["Type"] = A.type
+ if(A.can_chromosome)
+ mutation_data["ValidChromos"] = jointext(A.valid_chrom_list, ", ")
+ mutation_data["AppliedChromo"] = A.chromosome_name
+ mutation_data["ValidStoredChromos"] = build_chrom_list(A)
+ else
+ mutation_data["Active"] = FALSE
+ mutation_data["Scrambled"] = FALSE
+ mutation_data["Class"] = MUT_NORMAL
+
+ // Technically NONE of these mutations should be MUT_EXTRA but this will
+ // catch any weird edge cases
+ // Assign icons by priority - MUT_EXTRA will ALSO be discovered, so it
+ // has a higher priority for icon/image assignment
+ if (mut_class == MUT_EXTRA)
+ mutation_data["Image"] = "dna_extra.gif"
+ else if(discovered)
+ mutation_data["Image"] = "dna_discovered.gif"
+ else
+ mutation_data["Image"] = "dna_undiscovered.gif"
+
+ tgui_occupant_mutations += list(mutation_data)
+
+ // ---------------------------------------------------------------------- //
+ // Now get additional/"extra" mutations that they shouldn't have by default
+ for(var/datum/mutation/human/HM in scanner_occupant.dna.mutations)
+ // If it's in the mutation index array, we've already catalogued this
+ // mutation and can safely skip over it. It really shouldn't be, but this
+ // will catch any weird edge cases
+ if(HM.type in scanner_occupant.dna.mutation_index)
+ continue
+
+ var/list/mutation_data = list()
+ var/text_sequence = GET_SEQUENCE(HM.type)
+
+ // These will all be active mutations. They're added by injector and their
+ // sequencing code can't be changed. They can only be nullified, which
+ // completely removes them.
+ var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
+
+ mutation_data["Alias"] = A.alias
+ mutation_data["Sequence"] = text_sequence
+ mutation_data["Discovered"] = TRUE
+ mutation_data["Quality"] = HM.quality
+ mutation_data["Source"] = "occupant"
+
+ mutation_data["Name"] = HM.name
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+
+ mutation_data["Active"] = TRUE
+ mutation_data["Scrambled"] = HM.scrambled
+ mutation_data["Class"] = HM.class
+ mutation_data["CanChromo"] = HM.can_chromosome
+ mutation_data["ByondRef"] = REF(HM)
+ mutation_data["Type"] = HM.type
+
+ if(HM.can_chromosome)
+ mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ")
+ mutation_data["AppliedChromo"] = HM.chromosome_name
+ mutation_data["ValidStoredChromos"] = build_chrom_list(HM)
+
+ // Nothing in this list should be undiscovered. Technically nothing
+ // should be anything but EXTRA. But we're just handling some edge cases.
+ if (HM.class == MUT_EXTRA)
+ mutation_data["Image"] = "dna_extra.gif"
+ else
+ mutation_data["Image"] = "dna_discovered.gif"
+
+ tgui_occupant_mutations += list(mutation_data)
+
+ // ------------------------------------------------------------------------ //
+ // Build the list of mutations stored within the DNA Console
+ for(var/datum/mutation/human/HM in stored_mutations)
+ var/list/mutation_data = list()
+
+ var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
+
+ mutation_data["Alias"] = A.alias
+ mutation_data["Name"] = HM.name
+ mutation_data["Source"] = "console"
+ mutation_data["Active"] = TRUE
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+ mutation_data["ByondRef"] = REF(HM)
+ mutation_data["Type"] = HM.type
+
+ mutation_data["CanChromo"] = HM.can_chromosome
+ if(HM.can_chromosome)
+ mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ")
+ mutation_data["AppliedChromo"] = HM.chromosome_name
+ mutation_data["ValidStoredChromos"] = build_chrom_list(HM)
+
+ tgui_console_mutations += list(mutation_data)
+
+ // ------------------------------------------------------------------------ //
+ // Build the list of chromosomes stored within the DNA Console
+ var/chrom_index = 1
+ for(var/obj/item/chromosome/CM in stored_chromosomes)
+ var/list/chromo_data = list()
+
+ chromo_data["Name"] = CM.name
+ chromo_data["Description"] = CM.desc
+ chromo_data["Index"] = chrom_index
+
+ tgui_console_chromosomes += list(chromo_data)
+ ++chrom_index
+
+ // ------------------------------------------------------------------------ //
+ // Build the list of mutations stored on any inserted diskettes
+ if(diskette)
+ for(var/datum/mutation/human/HM in diskette.mutations)
+ var/list/mutation_data = list()
+
+ var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
+
+ mutation_data["Alias"] = A.alias
+ mutation_data["Name"] = HM.name
+ mutation_data["Active"] = TRUE
+ //mutation_data["Sequence"] = GET_SEQUENCE(HM.type)
+ mutation_data["Source"] = "disk"
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+ mutation_data["ByondRef"] = REF(HM)
+ mutation_data["Type"] = HM.type
+
+ mutation_data["CanChromo"] = HM.can_chromosome
+ if(HM.can_chromosome)
+ mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ")
+ mutation_data["AppliedChromo"] = HM.chromosome_name
+ mutation_data["ValidStoredChromos"] = build_chrom_list(HM)
+
+ tgui_diskette_mutations += list(mutation_data)
+
+ // ------------------------------------------------------------------------ //
+ // Build the list of mutations stored within any Advanced Injectors
+ if(LAZYLEN(injector_selection))
+ for(var/I in injector_selection)
+ var/list/mutations = list()
+ for(var/datum/mutation/human/HM in injector_selection[I])
+ var/list/mutation_data = list()
+
+ var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
+
+ mutation_data["Alias"] = A.alias
+ mutation_data["Name"] = HM.name
+ mutation_data["Active"] = TRUE
+ //mutation_data["Sequence"] = GET_SEQUENCE(HM.type)
+ mutation_data["Source"] = "injector"
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+ mutation_data["ByondRef"] = REF(HM)
+ mutation_data["Type"] = HM.type
+
+ if(HM.can_chromosome)
+ mutation_data["AppliedChromo"] = HM.chromosome_name
+
+ mutations += list(mutation_data)
+ tgui_advinjector_mutations += list(list(
+ "name" = "[I]",
+ "mutations" = mutations,
+ ))
+
+/**
+ * Takes any given chromosome and calculates chromosome compatibility
+ *
+ * Will iterate over the stored chromosomes in the DNA Console and will check
+ * whether it can be applied to the supplied mutation. Then returns a list of
+ * names of chromosomes that were compatible.
+ *
+ * Arguments:
+ * * mutation - The mutation to check chromosome compatibility with
+ */
+/obj/machinery/computer/scan_consolenew/proc/build_chrom_list(mutation)
+ var/list/chromosomes = list()
+
+ for(var/obj/item/chromosome/CM in stored_chromosomes)
+ if(CM.can_apply(mutation))
+ chromosomes += CM.name
+
+ return chromosomes
+
+/**
+ * Checks whether a mutation alias has been discovered
+ *
+ * Checks whether a given mutation's genetic sequence has been completed and
+ * discovers it if appropriate
+ *
+ * Arguments:
+ * * alias - Alias of the mutation to check (ie "Mutation 51" or "Mutation 12")
+ */
+/obj/machinery/computer/scan_consolenew/proc/check_discovery(alias)
+ // Note - All code paths that call this have already done checks on the
+ // current occupant to prevent cheese and other abuses. If you call this
+ // proc please also do the following checks first:
+ // if(!can_modify_occupant())
+ // return
+ // if(!(scanner_occupant == connected_scanner.occupant))
+ // return
+
+ // Turn the alias ("Mutation 1", "Mutation 35") into a mutation path
+ var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias)
+
+ // Check to see if this mutation is in the active mutation list. If it isn't,
+ // then the mutation isn't eligible for discovery. If it is but is scrambled,
+ // then the mutation isn't eligible for discovery. Finally, check if the
+ // mutation is in discovered mutations - If it isn't, add it to discover.
+ var/datum/mutation/human/M = scanner_occupant.dna.get_mutation(path)
+ if(!M)
+ return FALSE
+ if(M.scrambled)
+ return FALSE
+ if(stored_research && !(path in stored_research.discovered_mutations))
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(path)
+ stored_research.discovered_mutations += path
+ say("Successfully discovered [HM.name].")
+ return TRUE
+
+ return FALSE
+
+/**
+ * Find a mutation from various storage locations via ATOM ref
+ *
+ * Takes an ATOM Ref and searches the appropriate mutation buffers and storage
+ * vars to try and find the associated mutation.
+ *
+ * Arguments:
+ * * ref - ATOM ref of the mutation to locate
+ * * target_flags - Flags for storage mediums to search, see #defines
+ */
+/obj/machinery/computer/scan_consolenew/proc/get_mut_by_ref(ref, target_flags)
+ var/mutation
+
+ // Assume the occupant is valid and the check has been carried out before
+ // calling this proc with the relevant flags.
+ if(target_flags & SEARCH_OCCUPANT)
+ mutation = (locate(ref) in scanner_occupant.dna.mutations)
+ if(mutation)
+ return mutation
+
+ if(target_flags & SEARCH_STORED)
+ mutation = (locate(ref) in stored_mutations)
+ if(mutation)
+ return mutation
+
+ if(diskette && (target_flags & SEARCH_DISKETTE))
+ mutation = (locate(ref) in diskette.mutations)
+ if(mutation)
+ return mutation
+
+ if(injector_selection && (target_flags & SEARCH_ADV_INJ))
+ for(var/I in injector_selection)
+ mutation = (locate(ref) in injector_selection["[I]"])
+ if(mutation)
+ return mutation
+
+ return null
+
+/**
+ * Creates a randomised accuracy value for the enzyme pulse functionality.
+ *
+ * Donor code from previous DNA Console iteration.
+ *
+ * Arguments:
+ * * position - Index of the intended enzyme element to pulse
+ * * radduration - Duration of intended radiation pulse
+ * * number_of_blocks - Number of individual data blocks in the pulsed enzyme
+ */
+/obj/machinery/computer/scan_consolenew/proc/randomize_radiation_accuracy(position, radduration, number_of_blocks)
+ var/val = round(gaussian(0, RADIATION_ACCURACY_MULTIPLIER/radduration) + position, 1)
+ return WRAP(val, 1, number_of_blocks+1)
+
+/**
+ * Scrambles an enzyme element value for the enzyme pulse functionality.
+ *
+ * Donor code from previous DNA Console iteration.
+ *
+ * Arguments:
+ * * input - Enzyme identity element to scramble, expected hex value
+ * * rs - Strength of radiation pulse, increases the range of possible outcomes
+ */
+/obj/machinery/computer/scan_consolenew/proc/scramble(input,rs)
var/length = length(input)
var/ran = gaussian(0, rs*RADIATION_STRENGTH_MULTIPLIER)
if(ran == 0)
@@ -940,98 +1950,71 @@
ran = -round(-ran) //positive, so ceiling it
return num2hex(WRAP(hex2num(input)+ran, 0, 16**length), length)
-/obj/machinery/computer/scan_consolenew/proc/randomize_radiation_accuracy(position, radduration, number_of_blocks)
- var/val = round(gaussian(0, RADIATION_ACCURACY_MULTIPLIER/radduration) + position, 1)
- return WRAP(val, 1, number_of_blocks+1)
+ /**
+ * Performs the enzyme radiation pulse.
+ *
+ * Donor code from previous DNA Console iteration. Called from process() when
+ * there is a radiation pulse in progress. Ends processing.
+ */
+/obj/machinery/computer/scan_consolenew/proc/rad_pulse()
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ // If we can't, abort the procedure.
+ if(!can_modify_occupant())
+ rad_pulse_index = 0
+ STOP_PROCESSING(SSobj, src)
+ return
-/obj/machinery/computer/scan_consolenew/proc/get_viable_occupant()
- var/mob/living/carbon/viable_occupant = null
- if(connected)
- viable_occupant = connected.occupant
- if(!istype(viable_occupant) || !viable_occupant.dna || HAS_TRAIT_NOT_FROM(viable_occupant, TRAIT_RADIMMUNE,BLOODSUCKER_TRAIT) || HAS_TRAIT(viable_occupant, TRAIT_NOCLONE))
- viable_occupant = null
- return viable_occupant
+ var/len = length_char(scanner_occupant.dna.uni_identity)
+ var/num = randomize_radiation_accuracy(rad_pulse_index, radduration + (connected_scanner.precision_coeff ** 2), len) //Each manipulator level above 1 makes randomization as accurate as selected time + manipulator lvl^2 //Value is this high for the same reason as with laser - not worth the hassle of upgrading if the bonus is low
+ var/hex = copytext_char(scanner_occupant.dna.uni_identity, num, num+1)
+ hex = scramble(hex, radstrength, radduration)
-/obj/machinery/computer/scan_consolenew/proc/apply_buffer(action,buffer_num)
- buffer_num = clamp(buffer_num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[buffer_num]
- var/mob/living/carbon/viable_occupant = get_viable_occupant()
- if(istype(buffer_slot))
- viable_occupant.radiation += rand(100/(connected.damage_coeff ** 2),250/(connected.damage_coeff ** 2))
- //15 and 40 are just magic numbers that were here before so i didnt touch them, they are initial boundaries of damage
- //Each laser level reduces damage by lvl^2, so no effect on 1 lvl, 4 times less damage on 2 and 9 times less damage on 3
- //Numbers are this high because other way upgrading laser is just not worth the hassle, and i cant think of anything better to inmrove
- switch(action)
- if(SCANNER_ACTION_UI)
- if(buffer_slot["UI"])
- viable_occupant.dna.uni_identity = buffer_slot["UI"]
- viable_occupant.updateappearance(mutations_overlay_update=1)
- if(SCANNER_ACTION_UE)
- if(buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- viable_occupant.real_name = buffer_slot["name"]
- viable_occupant.name = buffer_slot["name"]
- viable_occupant.dna.unique_enzymes = buffer_slot["UE"]
- viable_occupant.dna.blood_type = buffer_slot["blood_type"]
- if(SCANNER_ACTION_MIXED)
- if(buffer_slot["UI"])
- viable_occupant.dna.uni_identity = buffer_slot["UI"]
- viable_occupant.updateappearance(mutations_overlay_update=1)
- if(buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- viable_occupant.real_name = buffer_slot["name"]
- viable_occupant.name = buffer_slot["name"]
- viable_occupant.dna.unique_enzymes = buffer_slot["UE"]
- viable_occupant.dna.blood_type = buffer_slot["blood_type"]
+ scanner_occupant.dna.uni_identity = copytext_char(scanner_occupant.dna.uni_identity, 1, num) + hex + copytext_char(scanner_occupant.dna.uni_identity, num + 1)
+ scanner_occupant.updateappearance(mutations_overlay_update=1)
-/obj/machinery/computer/scan_consolenew/proc/on_scanner_close()
- if(delayed_action && get_viable_occupant())
- to_chat(connected.occupant, "[src] activates!")
- apply_buffer(delayed_action["action"],delayed_action["buffer"])
- delayed_action = null //or make it stick + reset button ?
+ rad_pulse_index = 0
+ STOP_PROCESSING(SSobj, src)
+ return
-/obj/machinery/computer/scan_consolenew/proc/get_valid_mutation(mutation)
- var/mob/living/carbon/C = get_viable_occupant()
- if(C)
- var/datum/mutation/human/HM = C.dna.get_mutation(mutation)
- if(HM)
- return HM
- for(var/datum/mutation/human/A in stored_mutations)
- if(A.type == mutation)
- return A
+/**
+ * Sets the default state for the tgui interface.
+ */
+/obj/machinery/computer/scan_consolenew/proc/set_default_state()
+ tgui_view_state["consoleMode"] = "storage"
+ tgui_view_state["storageMode"] = "console"
+ tgui_view_state["storageConsSubMode"] = "mutations"
+ tgui_view_state["storageDiskSubMode"] = "mutations"
+/**
+ * Ejects the DNA Disk from the console.
+ *
+ * Will insert into the user's hand if possible, otherwise will drop it at the
+ * console's location.
+ *
+ * Arguments:
+ * * user - The mob that is attempting to eject the diskette.
+ */
+/obj/machinery/computer/scan_consolenew/proc/eject_disk(mob/user)
+ // Check for diskette.
+ if(!diskette)
+ return
-/obj/machinery/computer/scan_consolenew/proc/get_mutation_list(include_storage) //Returns a list of the mutation index types and any extra mutations
- var/mob/living/carbon/viable_occupant = get_viable_occupant()
- var/list/paths = list()
- if(viable_occupant)
- for(var/A in viable_occupant.dna.mutation_index)
- paths += A
- for(var/datum/mutation/human/A in viable_occupant.dna.mutations)
- if(A.class == MUT_EXTRA)
- paths += A.type
- if(include_storage)
- for(var/datum/mutation/human/A in stored_mutations)
- paths += A.type
- return paths
+ to_chat(user, "You eject [diskette] from [src].")
-/obj/machinery/computer/scan_consolenew/proc/get_valid_gene_string(mutation)
- var/mob/living/carbon/C = get_viable_occupant()
- if(C && (mutation in C.dna.mutation_index))
- return GET_GENE_STRING(mutation, C.dna)
- else if(C && (LAZYLEN(C.dna.mutations)))
- for(var/datum/mutation/human/A in C.dna.mutations)
- if(A.type == mutation)
- return GET_SEQUENCE(mutation)
- for(var/datum/mutation/human/A in stored_mutations)
- if(A.type == mutation)
- return GET_SEQUENCE(mutation)
+ // Reset the state to console storage.
+ tgui_view_state["storageMode"] = "console"
+
+ // If the disk shouldn't pop into the user's hand for any reason, drop it on the console instead.
+ if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(diskette))
+ diskette.forceMove(drop_location())
+ diskette = null
-/obj/machinery/computer/scan_consolenew/proc/discover(mutation)
- if(stored_research && !(mutation in stored_research.discovered_mutations))
- stored_research.discovered_mutations += mutation
- return TRUE
-/////////////////////////// DNA MACHINES
#undef INJECTOR_TIMEOUT
#undef NUMBER_OF_BUFFERS
+#undef SCRAMBLE_TIMEOUT
+#undef JOKER_TIMEOUT
+#undef JOKER_UPGRADE
#undef RADIATION_STRENGTH_MAX
#undef RADIATION_STRENGTH_MULTIPLIER
@@ -1041,11 +2024,9 @@
#undef RADIATION_IRRADIATION_MULTIPLIER
-#undef SCANNER_ACTION_SE
-#undef SCANNER_ACTION_UI
-#undef SCANNER_ACTION_UE
-#undef SCANNER_ACTION_MIXED
+#undef STATUS_TRANSFORMING
-//#undef BAD_MUTATION_DIFFICULTY
-//#undef GOOD_MUTATION_DIFFICULTY
-//#undef OP_MUTATION_DIFFICULTY
+#undef SEARCH_OCCUPANT
+#undef SEARCH_STORED
+#undef SEARCH_DISKETTE
+#undef SEARCH_ADV_INJ
diff --git a/code/game/machinery/computer/launchpad_control.dm b/code/game/machinery/computer/launchpad_control.dm
index 1924cd9f23..b2f7ae73a1 100644
--- a/code/game/machinery/computer/launchpad_control.dm
+++ b/code/game/machinery/computer/launchpad_control.dm
@@ -53,10 +53,10 @@
var/obj/machinery/launchpad/pad = launchpads[number]
return pad
-/obj/machinery/computer/launchpad/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/launchpad/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "launchpad_console", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "LaunchpadConsole", name)
ui.open()
/obj/machinery/computer/launchpad/ui_data(mob/user)
@@ -128,4 +128,4 @@
if("pull")
teleport(usr, current_pad, FALSE)
. = TRUE
- . = TRUE
\ No newline at end of file
+ . = TRUE
diff --git a/code/game/machinery/computer/prisoner/gulag_teleporter.dm b/code/game/machinery/computer/prisoner/gulag_teleporter.dm
index ca75ff1dd0..13727b585b 100644
--- a/code/game/machinery/computer/prisoner/gulag_teleporter.dm
+++ b/code/game/machinery/computer/prisoner/gulag_teleporter.dm
@@ -6,8 +6,6 @@
icon_keyboard = "security_key"
req_access = list(ACCESS_ARMORY)
circuit = /obj/item/circuitboard/computer/gulag_teleporter_console
- ui_x = 350
- ui_y = 295
var/default_goal = 200
var/obj/machinery/gulag_teleporter/teleporter = null
@@ -21,11 +19,10 @@
. = ..()
scan_machinery()
-/obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "gulag_console", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "GulagTeleporterConsole", name)
ui.open()
/obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_data(mob/user)
diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm
index 2621616759..401d05da12 100644
--- a/code/game/machinery/computer/robot.dm
+++ b/code/game/machinery/computer/robot.dm
@@ -5,163 +5,122 @@
icon_keyboard = "rd_key"
req_access = list(ACCESS_ROBOTICS)
circuit = /obj/item/circuitboard/computer/robotics
- var/temp = null
-
light_color = LIGHT_COLOR_PINK
+ ui_x = 500
+ ui_y = 460
/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R)
+ . = FALSE
if(!istype(R))
- return FALSE
+ return
if(isAI(user))
- if (R.connected_ai != user)
- return FALSE
+ if(R.connected_ai != user)
+ return
if(iscyborg(user))
- if (R != user)
- return FALSE
+ if(R != user)
+ return
if(R.scrambledcodes)
- return FALSE
- if (hasSiliconAccessInArea(user) && !issilicon(user))
- if (!Adjacent(user))
- return FALSE
+ return
return TRUE
-/obj/machinery/computer/robotics/ui_interact(mob/user)
- . = ..()
- if (src.z > 6)
- to_chat(user, "Unable to establish a connection: \black You're too far away from the station!")
- return
- user.set_machine(src)
- var/dat
- var/robots = 0
+/obj/machinery/computer/robotics/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "RoboticsControlConsole", name)
+ ui.open()
+
+/obj/machinery/computer/robotics/ui_data(mob/user)
+ var/list/data = list()
+
+ data["can_hack"] = FALSE
+ if(issilicon(user))
+ var/mob/living/silicon/S = user
+ if(S.hack_software)
+ data["can_hack"] = TRUE
+ else if(IsAdminGhost(user))
+ data["can_hack"] = TRUE
+
+ data["cyborgs"] = list()
for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs)
if(!can_control(user, R))
continue
- robots++
- dat += "[R.name] |"
- if(R.stat)
- dat += " Not Responding |"
- else if(R.locked_down)
- dat += " Locked Down |"
- else
- dat += " Operating Normally |"
- if(R.cell)
- dat += " Battery Installed ([R.cell.charge]/[R.cell.maxcharge]) |"
- else
- dat += " No Cell Installed |"
- if(R.module)
- dat += " Module Installed ([R.module.name]) |"
- else
- dat += " No Module Installed |"
- if(R.connected_ai)
- dat += " Slaved to [R.connected_ai.name] |"
- else
- dat += " Independent from AI |"
- if(issilicon(user) && user != R)
- var/mob/living/silicon/S = user
- if(is_servant_of_ratvar(S))
- dat += "(Convert) "
- else if(S.hack_software && !R.emagged)
- dat += "(Hack) "
- else if(IsAdminGhost(user) && !R.emagged)
- dat += "(Hack) "
- dat += "([R.locked_down? "Lockdown" : "Release"]) "
- dat += "(Destroy)"
- dat += " "
+ if(z != (get_turf(R)).z)
+ continue
+ var/list/cyborg_data = list(
+ name = R.name,
+ locked_down = R.locked_down,
+ status = R.stat,
+ charge = R.cell ? round(R.cell.percent()) : null,
+ module = R.module ? "[R.module.name] Module" : "No Module Detected",
+ synchronization = R.connected_ai,
+ emagged = R.emagged,
+ ref = REF(R)
+ )
+ data["cyborgs"] += list(cyborg_data)
- if(!robots)
- dat += "No Cyborg Units detected within access parameters."
- dat += " "
-
- var/drones = 0
+ data["drones"] = list()
for(var/mob/living/simple_animal/drone/D in GLOB.drones_list)
if(D.hacked)
continue
- drones++
- dat += "[D.name] |"
- if(D.stat)
- dat += " Not Responding |"
- dat += "(Destroy)"
- dat += " "
+ if(z != (get_turf(D)).z)
+ continue
+ var/list/drone_data = list(
+ name = D.name,
+ status = D.stat,
+ ref = REF(D)
+ )
+ data["drones"] += list(drone_data)
- if(!drones)
- dat += "No Drone Units detected within access parameters."
+ return data
- var/datum/browser/popup = new(user, "computer", "Cyborg Control Console", 400, 500)
- popup.set_content(dat)
- popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
- popup.open()
- return
-
-/obj/machinery/computer/robotics/Topic(href, href_list)
+/obj/machinery/computer/robotics/ui_act(action, params)
if(..())
return
- if (href_list["temp"])
- src.temp = null
-
- else if (href_list["killbot"])
- if(src.allowed(usr))
- var/mob/living/silicon/robot/R = locate(href_list["killbot"]) in GLOB.silicon_mobs
- if(can_control(usr, R))
- var/choice = input("Are you certain you wish to detonate [R.name]?") in list("Confirm", "Abort")
- if(choice == "Confirm" && can_control(usr, R) && !..())
+ switch(action)
+ if("killbot")
+ if(allowed(usr))
+ var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs
+ if(can_control(usr, R) && !..())
var/turf/T = get_turf(R)
message_admins("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(R, R.client)] at [ADMIN_VERBOSEJMP(T)]!")
log_game("\[key_name(usr)] detonated [key_name(R)]!")
if(R.connected_ai)
to_chat(R.connected_ai, "
You have been assigned to collect garbage from trash bins, located around the station. The crewmembers will put their trash into it and you will collect the said trash.
There is a recycling machine near your closet, inside maintenance; use it to recycle the trash for a small chance to get useful minerals. Then deliver these minerals to cargo or engineering. You are our last hope for a clean station, do not screw this up!"
+ info = "_New Assignment_\n\n You have been assigned to collect garbage from trash bins, located around the station. The crewmembers will put their trash into it and you will collect the said trash.
"
- return output
-
-/obj/machinery/mecha_part_fabricator/proc/check_clearance(datum/design/D)
- if(!(obj_flags & EMAGGED) && (offstation_security_levels || is_station_level(z)) && !ISINRANGE(GLOB.security_level, D.min_security_level, D.max_security_level))
- return FALSE
- return TRUE
-
-/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D)
- var/clearance = !(obj_flags & EMAGGED) && (offstation_security_levels || is_station_level(z))
- var/sec_text = ""
- if(clearance && (D.min_security_level > SEC_LEVEL_GREEN || D.max_security_level < SEC_LEVEL_DELTA))
- sec_text = " (Allowed security levels: "
- for(var/n in D.min_security_level to D.max_security_level)
- sec_text += NUM2SECLEVEL(n)
- if(n + 1 <= D.max_security_level)
- sec_text += ", "
- sec_text += ") "
- var/output = "[initial(D.name)] (Cost: [output_part_cost(D)]) [sec_text][get_construction_time_w_coeff(D)/10]sec"
- return output
-
-/obj/machinery/mecha_part_fabricator/proc/output_part_cost(datum/design/D)
- var/i = 0
- var/output
+/**
+ * Generates an info list for a given part.
+ *
+ * Returns a list of part information.
+ * * D - Design datum to get information on.
+ * * categories - Boolean, whether or not to parse snowflake categories into the part information list.
+ */
+/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D, categories = FALSE)
+ var/cost = list()
for(var/c in D.materials)
var/datum/material/M = c
- output += "[i?" | ":null][get_resource_cost_w_coeff(D, M)] [M.name]"
- i++
- return output
+ cost[M.name] = get_resource_cost_w_coeff(D, M)
+ var/obj/built_item = D.build_path
+
+ var/list/category_override = null
+ var/list/sub_category = null
+
+ if(categories)
+ // Handle some special cases to build up sub-categories for the fab interface.
+ // Start with checking if this design builds a cyborg module.
+ if(built_item in typesof(/obj/item/borg/upgrade))
+ var/obj/item/borg/upgrade/U = built_item
+ var/module_types = initial(U.module_flags)
+ sub_category = list()
+ if(module_types)
+ if(module_types & BORG_MODULE_SECURITY)
+ sub_category += "Security"
+ if(module_types & BORG_MODULE_MINER)
+ sub_category += "Mining"
+ if(module_types & BORG_MODULE_JANITOR)
+ sub_category += "Janitor"
+ if(module_types & BORG_MODULE_MEDICAL)
+ sub_category += "Medical"
+ if(module_types & BORG_MODULE_ENGINEERING)
+ sub_category += "Engineering"
+ else
+ sub_category += "All Cyborgs"
+ // Else check if this design builds a piece of exosuit equipment.
+ else if(built_item in typesof(/obj/item/mecha_parts/mecha_equipment))
+ var/obj/item/mecha_parts/mecha_equipment/E = built_item
+ var/mech_types = initial(E.mech_flags)
+ sub_category = "Equipment"
+ if(mech_types)
+ category_override = list()
+ if(mech_types & EXOSUIT_MODULE_RIPLEY)
+ category_override += "Ripley"
+ if(mech_types & EXOSUIT_MODULE_FIREFIGHTER)
+ category_override += "Firefighter"
+ if(mech_types & EXOSUIT_MODULE_ODYSSEUS)
+ category_override += "Odysseus"
+ // if(mech_types & EXOSUIT_MODULE_CLARKE)
+ // category_override += "Clarke"
+ if(mech_types & EXOSUIT_MODULE_GYGAX_MED)
+ category_override += "Medical-Spec Gygax"
+ if(mech_types & EXOSUIT_MODULE_GYGAX)
+ category_override += "Gygax"
+ if(mech_types & EXOSUIT_MODULE_DURAND)
+ category_override += "Durand"
+ if(mech_types & EXOSUIT_MODULE_HONK)
+ category_override += "H.O.N.K"
+ if(mech_types & EXOSUIT_MODULE_PHAZON)
+ category_override += "Phazon"
+
+
+ var/list/part = list(
+ "name" = D.name,
+ "desc" = initial(built_item.desc),
+ "printTime" = get_construction_time_w_coeff(initial(D.construction_time))/10,
+ "cost" = cost,
+ "id" = D.id,
+ "subCategory" = sub_category,
+ "categoryOverride" = category_override,
+ "searchMeta" = "UNKNOWN"//D.search_metadata
+ )
+
+ return part
+
+/**
+ * Generates a list of resources / materials available to this Exosuit Fab
+ *
+ * Returns null if there is no material container available.
+ * List format is list(material_name = list(amount = ..., ref = ..., etc.))
+ */
/obj/machinery/mecha_part_fabricator/proc/output_available_resources()
- var/output
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- for(var/mat_id in materials.materials)
- var/datum/material/M = mat_id
- var/amount = materials.materials[mat_id]
- output += "[M.name]: [amount] cm³"
- if(amount >= MINERAL_MATERIAL_AMOUNT)
- output += "- Remove \[1\]"
- if(amount >= (MINERAL_MATERIAL_AMOUNT * 10))
- output += " | \[10\]"
- output += " | \[All\]"
- output += " "
- return output
+ var/datum/component/material_container/materials = rmat.mat_container
+ var/list/material_data = list()
+
+ if(materials)
+ for(var/mat_id in materials.materials)
+ var/datum/material/M = mat_id
+ var/list/material_info = list()
+ var/amount = materials.materials[mat_id]
+
+ material_info = list(
+ "name" = M.name,
+ "ref" = REF(M),
+ "amount" = amount,
+ "sheets" = round(amount / MINERAL_MATERIAL_AMOUNT),
+ "removable" = amount >= MINERAL_MATERIAL_AMOUNT
+ )
+
+ material_data += list(material_info)
+
+ return material_data
+
+ return null
+
+/**
+ * Intended to be called when an item starts printing.
+ *
+ * Adds the overlay to show the fab working and sets active power usage settings.
+ */
+/obj/machinery/mecha_part_fabricator/proc/on_start_printing()
+ add_overlay("fab-active")
+ use_power = ACTIVE_POWER_USE
+
+/**
+ * Intended to be called when the exofab has stopped working and is no longer printing items.
+ *
+ * Removes the overlay to show the fab working and sets idle power usage settings. Additionally resets the description and turns off queue processing.
+ */
+/obj/machinery/mecha_part_fabricator/proc/on_finish_printing()
+ cut_overlay("fab-active")
+ use_power = IDLE_POWER_USE
+ desc = initial(desc)
+ process_queue = FALSE
+
+/**
+ * Calculates resource/material costs for printing an item based on the machine's resource coefficient.
+ *
+ * Returns a list of k,v resources with their amounts.
+ * * D - Design datum to calculate the modified resource cost of.
+ */
/obj/machinery/mecha_part_fabricator/proc/get_resources_w_coeff(datum/design/D)
var/list/resources = list()
for(var/R in D.materials)
@@ -164,294 +247,419 @@
resources[M] = get_resource_cost_w_coeff(D, M)
return resources
+/**
+ * Checks if the Exofab has enough resources to print a given item.
+ *
+ * Returns FALSE if the design has no reagents used in its construction (?) or if there are insufficient resources.
+ * Returns TRUE if there are sufficient resources to print the item.
+ * * D - Design datum to calculate the modified resource cost of.
+ */
/obj/machinery/mecha_part_fabricator/proc/check_resources(datum/design/D)
- if(D.reagents_list.len) // No reagents storage - no reagent designs.
+ if(length(D.reagents_list)) // No reagents storage - no reagent designs.
return FALSE
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
+ var/datum/component/material_container/materials = rmat.mat_container
if(materials.has_materials(get_resources_w_coeff(D)))
return TRUE
return FALSE
-/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D)
+/**
+ * Attempts to build the next item in the build queue.
+ *
+ * Returns FALSE if either there are no more parts to build or the next part is not buildable.
+ * Returns TRUE if the next part has started building.
+ * * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
+ */
+/obj/machinery/mecha_part_fabricator/proc/build_next_in_queue(verbose = TRUE)
+ if(!length(queue))
+ return FALSE
+
+ var/datum/design/D = queue[1]
+ if(build_part(D, verbose))
+ remove_from_queue(1)
+ return TRUE
+
+ return FALSE
+
+/**
+ * Starts the build process for a given design datum.
+ *
+ * Returns FALSE if the procedure fails. Returns TRUE when being_built is set.
+ * Uses materials.
+ * * D - Design datum to attempt to print.
+ * * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
+ */
+/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D, verbose = TRUE)
+ if(!D)
+ return FALSE
+
+ var/datum/component/material_container/materials = rmat.mat_container
+ if (!materials)
+ if(verbose)
+ say("No access to material storage, please contact the quartermaster.")
+ return FALSE
+ if (rmat.on_hold())
+ if(verbose)
+ say("Mineral access is on hold, please contact the quartermaster.")
+ return FALSE
+ if(!check_resources(D))
+ if(verbose)
+ say("Not enough resources. Processing stopped.")
+ return FALSE
+
+ build_materials = get_resources_w_coeff(D)
+
+ materials.use_materials(build_materials)
being_built = D
- desc = "It's building \a [initial(D.name)]."
- var/list/res_coef = get_resources_w_coeff(D)
+ build_finish = world.time + get_construction_time_w_coeff(initial(D.construction_time))
+ build_start = world.time
+ desc = "It's building \a [D.name]."
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- materials.use_materials(res_coef)
- add_overlay("fab-active")
- use_power = ACTIVE_POWER_USE
- updateUsrDialog()
- sleep(get_construction_time_w_coeff(D))
- use_power = IDLE_POWER_USE
- cut_overlay("fab-active")
- desc = initial(desc)
+ rmat.silo_log(src, "built", -1, "[D.name]", build_materials)
- var/location = get_step(src,(dir))
- var/obj/item/I = new D.build_path(location)
- I.set_custom_materials(res_coef)
- say("\The [I] is complete.")
- being_built = null
-
- updateUsrDialog()
return TRUE
-/obj/machinery/mecha_part_fabricator/proc/update_queue_on_page()
- send_byjax(usr,"mecha_fabricator.browser","queue",list_queue())
- return
+/obj/machinery/mecha_part_fabricator/process()
+ // If there's a stored part to dispense due to an obstruction, try to dispense it.
+ if(stored_part)
+ var/turf/exit = get_step(src,(dir))
+ if(exit.density)
+ return TRUE
-/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(set_name)
- if(set_name in part_sets)
- for(var/v in stored_research.researched_designs)
- var/datum/design/D = SSresearch.techweb_design_by_id(v)
- if(D.build_type & MECHFAB)
- if(set_name in D.category)
- add_to_queue(D)
+ say("Obstruction cleared. \The [stored_part] is complete.")
+ stored_part.forceMove(exit)
+ stored_part = null
-/obj/machinery/mecha_part_fabricator/proc/add_to_queue(D)
+ // If there's nothing being built, try to build something
+ if(!being_built)
+ // If we're not processing the queue anymore or there's nothing to build, end processing.
+ if(!process_queue || !build_next_in_queue())
+ on_finish_printing()
+ STOP_PROCESSING(SSfastprocess, src)
+ //end_processing()
+ return TRUE
+ on_start_printing()
+
+ // If there's an item being built, check if it is complete.
+ if(being_built && (build_finish < world.time))
+ // Then attempt to dispense it and if appropriate build the next item.
+ dispense_built_part(being_built)
+ if(process_queue)
+ build_next_in_queue(FALSE)
+ return TRUE
+
+/**
+ * Dispenses a part to the tile infront of the Exosuit Fab.
+ *
+ * Returns FALSE is the machine cannot dispense the part on the appropriate turf.
+ * Return TRUE if the part was successfully dispensed.
+ * * D - Design datum to attempt to dispense.
+ */
+/obj/machinery/mecha_part_fabricator/proc/dispense_built_part(datum/design/D)
+ var/obj/item/I = new D.build_path(src)
+ // I.material_flags |= MATERIAL_NO_EFFECTS //Find a better way to do this.
+ I.set_custom_materials(build_materials)
+
+ being_built = null
+
+ var/turf/exit = get_step(src,(dir))
+ if(exit.density)
+ say("Error! Part outlet is obstructed.")
+ desc = "It's trying to dispense \a [D.name], but the part outlet is obstructed."
+ stored_part = I
+ return FALSE
+
+ say("\The [I] is complete.")
+ I.forceMove(exit)
+ return TRUE
+
+/**
+ * Adds a list of datum designs to the build queue.
+ *
+ * Will only add designs that are in this machine's stored techweb.
+ * Does final checks for datum IDs and makes sure this machine can build the designs.
+ * * part_list - List of datum design ids for designs to add to the queue.
+ */
+/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(list/part_list)
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ if((D.build_type & MECHFAB) && (D.id in part_list))
+ add_to_queue(D)
+
+/**
+ * Adds a datum design to the build queue.
+ *
+ * Returns TRUE if successful and FALSE if the design was not added to the queue.
+ * * D - Datum design to add to the queue.
+ */
+/obj/machinery/mecha_part_fabricator/proc/add_to_queue(datum/design/D)
if(!istype(queue))
queue = list()
if(D)
queue[++queue.len] = D
- return queue.len
+ return TRUE
+ return FALSE
+/**
+ * Removes datum design from the build queue based on index.
+ *
+ * Returns TRUE if successful and FALSE if a design was not removed from the queue.
+ * * index - Index in the build queue of the element to remove.
+ */
/obj/machinery/mecha_part_fabricator/proc/remove_from_queue(index)
- if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>queue.len))
+ if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>length(queue)))
return FALSE
queue.Cut(index,++index)
return TRUE
-/obj/machinery/mecha_part_fabricator/proc/process_queue()
- var/datum/design/D = queue[1]
- if(!D)
- remove_from_queue(1)
- if(queue.len)
- return process_queue()
- else
- return
- temp = null
- while(D)
- if(stat&(NOPOWER|BROKEN))
- return FALSE
- if(!check_clearance(D))
- say("Security level not met. Queue processing stopped.")
- temp = {"Security level not met to build next part.
- Try again | Return"}
- return FALSE
- if(!check_resources(D))
- say("Not enough resources. Queue processing stopped.")
- temp = {"Not enough resources to build next part.
- Try again | Return"}
- return FALSE
- remove_from_queue(1)
- build_part(D)
- D = listgetindex(queue, 1)
- say("Queue processing finished successfully.")
-
+/**
+ * Generates a list of parts formatted for tgui based on the current build queue.
+ *
+ * Returns a formatted list of lists containing formatted part information for every part in the build queue.
+ */
/obj/machinery/mecha_part_fabricator/proc/list_queue()
- var/output = "Queue contains:"
- if(!istype(queue) || !queue.len)
- output += " Nothing"
- else
- output += ""
- var/i = 0
- for(var/datum/design/D in queue)
- i++
- var/obj/part = D.build_path
- output += "
\n" // less lines than in woundscan() so we don't overload people trying to get basic med info
+ msg += "\n"
for(var/thing in M.diseases)
var/datum/disease/D = thing
@@ -414,7 +436,7 @@ GENETICS SCANNER
var/blood_typepath = C.get_blood_id()
if(blood_typepath)
if(ishuman(C))
- if(H.bleed_rate)
+ if(H.is_bleeding())
msg += "Subject is bleeding!\n"
var/blood_percent = round((C.scan_blood_volume() / (BLOOD_VOLUME_NORMAL * C.blood_ratio))*100)
var/blood_type = C.dna.blood_type
@@ -505,6 +527,67 @@ GENETICS SCANNER
desc = "A hand-held body scanner able to distinguish vital signs of the subject with high accuracy."
advanced = TRUE
+/// Displays wounds with extended information on their status vs medscanners
+/proc/woundscan(mob/user, mob/living/carbon/patient, obj/item/healthanalyzer/wound/scanner)
+ if(!istype(patient))
+ return
+
+ var/render_list = ""
+ for(var/i in patient.get_wounded_bodyparts())
+ var/obj/item/bodypart/wounded_part = i
+ render_list += "Warning: Physical trauma[LAZYLEN(wounded_part.wounds) > 1? "s" : ""] detected in [wounded_part.name]"
+ for(var/k in wounded_part.wounds)
+ var/datum/wound/W = k
+ render_list += "
[W.get_scanner_description()]
\n"
+ render_list += ""
+
+ if(render_list == "")
+ if(istype(scanner))
+ // Only emit the cheerful scanner message if this scan came from a scanner
+ playsound(scanner, 'sound/machines/ping.ogg', 50, FALSE)
+ to_chat(user, "\The [scanner] makes a happy ping and briefly displays a smiley face with several exclamation points! It's really excited to report that [patient] has no wounds!")
+ else
+ to_chat(user, "No wounds detected in subject.")
+ else
+ to_chat(user, jointext(render_list, ""))
+
+/obj/item/healthanalyzer/wound
+ name = "first aid analyzer"
+ icon_state = "adv_spectrometer"
+ desc = "A prototype MeLo-Tech medical scanner used to diagnose injuries and recommend treatment for serious wounds, but offers no further insight into the patient's health. You hope the final version is less annoying to read!"
+ var/next_encouragement
+ var/greedy
+
+/obj/item/healthanalyzer/wound/attack_self(mob/user)
+ if(next_encouragement < world.time)
+ playsound(src, 'sound/machines/ping.ogg', 50, FALSE)
+ var/list/encouragements = list("briefly displays a happy face, gazing emptily at you", "briefly displays a spinning cartoon heart", "displays an encouraging message about eating healthy and exercising", \
+ "reminds you that everyone is doing their best", "displays a message wishing you well", "displays a sincere thank-you for your interest in first-aid", "formally absolves you of all your sins")
+ to_chat(user, "\The [src] makes a happy ping and [pick(encouragements)]!")
+ next_encouragement = world.time + 10 SECONDS
+ greedy = FALSE
+ else if(!greedy)
+ to_chat(user, "\The [src] displays an eerily high-definition frowny face, chastizing you for asking it for too much encouragement.")
+ greedy = TRUE
+ else
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
+ if(isliving(user))
+ var/mob/living/L = user
+ to_chat(L, "\The [src] makes a disappointed buzz and pricks your finger for being greedy. Ow!")
+ L.adjustBruteLoss(4)
+ L.dropItemToGround(src)
+
+/obj/item/healthanalyzer/wound/attack(mob/living/carbon/patient, mob/living/carbon/human/user)
+ add_fingerprint(user)
+ user.visible_message("[user] scans [patient] for serious injuries.", "You scan [patient] for serious injuries.")
+
+ if(!istype(patient))
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
+ to_chat(user, "\The [src] makes a sad buzz and briefly displays a frowny face, indicating it can't scan [patient].")
+ return
+
+ woundscan(user, patient, src)
+
/obj/item/analyzer
desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function."
name = "analyzer"
@@ -541,54 +624,13 @@ GENETICS SCANNER
if (user.stat || user.eye_blind)
return
- var/turf/location = user.loc
+ //Functionality moved down to proc/scan_turf()
+ var/turf/location = get_turf(user)
if(!istype(location))
return
-
- var/datum/gas_mixture/environment = location.return_air()
-
- var/pressure = environment.return_pressure()
- var/total_moles = environment.total_moles()
-
- to_chat(user, "Results:")
- if(abs(pressure - ONE_ATMOSPHERE) < 10)
- to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
- else
- to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
- if(total_moles)
-
- var/o2_concentration = environment.get_moles(/datum/gas/oxygen)/total_moles
- var/n2_concentration = environment.get_moles(/datum/gas/nitrogen)/total_moles
- var/co2_concentration = environment.get_moles(/datum/gas/carbon_dioxide)/total_moles
- var/plasma_concentration = environment.get_moles(/datum/gas/plasma)/total_moles
-
- if(abs(n2_concentration - N2STANDARD) < 20)
- to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)")
- else
- to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)")
-
- if(abs(o2_concentration - O2STANDARD) < 2)
- to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)")
- else
- to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)")
-
- if(co2_concentration > 0.01)
- to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)")
- else
- to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)")
-
- if(plasma_concentration > 0.005)
- to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)")
- else
- to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)")
-
- for(var/id in environment.get_gases())
- if(id in GLOB.hardcoded_gases)
- continue
- var/gas_concentration = environment.get_moles(id)/total_moles
- to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(environment.get_moles(id), 0.01)] mol)")
- to_chat(user, "Temperature: [round(environment.return_temperature()-T0C, 0.01)] °C ([round(environment.return_temperature(), 0.01)] K)")
-
+
+ scan_turf(user, location)
+
/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens
. = ..()
@@ -666,7 +708,7 @@ GENETICS SCANNER
var/total_moles = air_contents.total_moles()
var/pressure = air_contents.return_pressure()
- var/volume = air_contents.return_volume()
+ var/volume = air_contents.return_volume() //could just do mixture.volume... but safety, I guess?
var/temperature = air_contents.return_temperature()
var/cached_scan_results = air_contents.analyzer_results
@@ -693,6 +735,73 @@ GENETICS SCANNER
to_chat(user, "Power of the last fusion reaction: [fusion_power]\n This power indicates it was a [tier]-tier fusion reaction.")
return
+/obj/item/analyzer/proc/scan_turf(mob/user, turf/location)
+
+ var/datum/gas_mixture/environment = location.return_air()
+
+ var/pressure = environment.return_pressure()
+ var/total_moles = environment.total_moles()
+ var/cached_scan_results = environment.analyzer_results
+
+ to_chat(user, "Results:")
+ if(abs(pressure - ONE_ATMOSPHERE) < 10)
+ to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
+ else
+ to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
+ if(total_moles)
+
+ var/o2_concentration = environment.get_moles(/datum/gas/oxygen)/total_moles
+ var/n2_concentration = environment.get_moles(/datum/gas/nitrogen)/total_moles
+ var/co2_concentration = environment.get_moles(/datum/gas/carbon_dioxide)/total_moles
+ var/plasma_concentration = environment.get_moles(/datum/gas/plasma)/total_moles
+
+ if(abs(n2_concentration - N2STANDARD) < 20)
+ to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)")
+ else
+ to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)")
+
+ if(abs(o2_concentration - O2STANDARD) < 2)
+ to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)")
+ else
+ to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)")
+
+ if(co2_concentration > 0.01)
+ to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)")
+ else
+ to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)")
+
+ if(plasma_concentration > 0.005)
+ to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)")
+ else
+ to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)")
+
+ for(var/id in environment.get_gases())
+ if(id in GLOB.hardcoded_gases)
+ continue
+ var/gas_concentration = environment.get_moles(id)/total_moles
+ to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(environment.get_moles(id), 0.01)] mol)")
+ to_chat(user, "Temperature: [round(environment.return_temperature()-T0C, 0.01)] °C ([round(environment.return_temperature(), 0.01)] K)")
+
+ if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected
+ var/fusion_power = round(cached_scan_results["fusion"], 0.01)
+ var/tier = fusionpower2text(fusion_power)
+ to_chat(user, "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.")
+ to_chat(user, "Power of the last fusion reaction: [fusion_power]\n This power indicates it was a [tier]-tier fusion reaction.")
+
+/obj/item/analyzer/ranged
+ desc = "A hand-held scanner which uses advanced spectroscopy and infrared readings to analyze gases as a distance. Alt-Click to use the built in barometer function."
+ name = "long-range analyzer"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "ranged_analyzer"
+
+/obj/item/analyzer/ranged/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(target.tool_act(user, src, tool_behaviour))
+ return
+ // Tool act didn't scan it, so let's get it's turf.
+ var/turf/location = get_turf(target)
+ scan_turf(user, location)
+
//slime scanner
/obj/item/slime_scanner
@@ -878,3 +987,9 @@ GENETICS SCANNER
return "[HM.name] ([HM.alias])"
else
return HM.alias
+
+#undef SCANMODE_HEALTH
+#undef SCANMODE_CHEMICAL
+#undef SCANMODE_WOUND
+#undef SCANNER_CONDENSED
+#undef SCANNER_VERBOSE
\ No newline at end of file
diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm
index 78a1a3bfda..cef06bfde8 100644
--- a/code/game/objects/items/devices/taperecorder.dm
+++ b/code/game/objects/items/devices/taperecorder.dm
@@ -54,8 +54,7 @@
mytape.ruin() //Fires destroy the tape
..()
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/taperecorder/attack_hand(mob/user)
+/obj/item/taperecorder/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user)
if(mytape)
if(!user.is_holding(src))
diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm
index b6261b9060..eb444cdb12 100644
--- a/code/game/objects/items/devices/traitordevices.dm
+++ b/code/game/objects/items/devices/traitordevices.dm
@@ -76,6 +76,9 @@ effective or pretty fucking useless.
var/used = 0 // is it cooling down?
var/stealth = FALSE
+ var/ui_x = 320
+ var/ui_y = 335
+
/obj/item/healthanalyzer/rad_laser/attack(mob/living/M, mob/living/user)
if(!stealth || !irradiate)
..()
@@ -83,93 +86,102 @@ effective or pretty fucking useless.
return
if(!used)
log_combat(user, M, "irradiated", src)
- var/cooldown = GetCooldown()
- used = 1
+ var/cooldown = get_cooldown()
+ used = TRUE
icon_state = "health1"
- handle_cooldown(cooldown) // splits off to handle the cooldown while handling wavelength
+ addtimer(VARSET_CALLBACK(src, used, FALSE), cooldown)
+ addtimer(VARSET_CALLBACK(src, icon_state, "health"), cooldown)
to_chat(user, "Successfully irradiated [M].")
- spawn((wavelength+(intensity*4))*5)
- if(M)
- if(intensity >= 5)
- M.apply_effect(round(intensity/0.075), EFFECT_UNCONSCIOUS)
- M.rad_act(intensity*10)
+ addtimer(CALLBACK(src, .proc/radiation_aftereffect, M), (wavelength+(intensity*4))*5)
else
to_chat(user, "The radioactive microlaser is still recharging.")
-/obj/item/healthanalyzer/rad_laser/proc/handle_cooldown(cooldown)
- spawn(cooldown)
- used = 0
- icon_state = "health"
+/obj/item/healthanalyzer/rad_laser/proc/radiation_aftereffect(mob/living/M)
+ if(QDELETED(M))
+ return
+ if(intensity >= 5)
+ M.apply_effect(round(intensity/0.075), EFFECT_UNCONSCIOUS)
+ M.rad_act(intensity*10)
+
+/obj/item/healthanalyzer/rad_laser/proc/get_cooldown()
+ return round(max(10, (stealth*30 + intensity*5 - wavelength/4)))
/obj/item/healthanalyzer/rad_laser/attack_self(mob/user)
interact(user)
-/obj/item/healthanalyzer/rad_laser/proc/GetCooldown()
- return round(max(10, (stealth*30 + intensity*5 - wavelength/4)))
-
/obj/item/healthanalyzer/rad_laser/interact(mob/user)
ui_interact(user)
-/obj/item/healthanalyzer/rad_laser/ui_interact(mob/user)
- . = ..()
+/obj/item/healthanalyzer/rad_laser/ui_state(mob/user)
+ return GLOB.hands_state
- var/dat = "Irradiation: [irradiate ? "On" : "Off"] "
- dat += "Stealth Mode (NOTE: Deactivates automatically while Irradiation is off): [stealth ? "On" : "Off"] "
- dat += "Scan Mode: "
- if(!scanmode)
- dat += "Scan Health"
- else if(scanmode == 1)
- dat += "Scan Reagents"
- else
- dat += "Disabled"
- dat += "
"
+/obj/item/healthanalyzer/rad_laser/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "RadioactiveMicrolaser")
+ ui.open()
- dat += {"
- Radiation Intensity:
- --
- [intensity]
- ++
+/obj/item/healthanalyzer/rad_laser/ui_data(mob/user)
+ var/list/data = list()
+ data["irradiate"] = irradiate
+ data["stealth"] = stealth
+ data["scanmode"] = scanmode
+ data["intensity"] = intensity
+ data["wavelength"] = wavelength
+ data["on_cooldown"] = used
+ data["cooldown"] = DisplayTimeText(get_cooldown())
+ return data
- Radiation Wavelength:
- --
- [(wavelength+(intensity*4))]
- ++
- Laser Cooldown: [DisplayTimeText(GetCooldown())]
- "}
+/obj/item/healthanalyzer/rad_laser/ui_act(action, params)
+ if(..())
+ return
- var/datum/browser/popup = new(user, "radlaser", "Radioactive Microlaser Interface", 400, 240)
- popup.set_content(dat)
- popup.open()
-
-/obj/item/healthanalyzer/rad_laser/Topic(href, href_list)
- if(!usr.canUseTopic(src))
- return 1
-
- usr.set_machine(src)
- if(href_list["rad"])
- irradiate = !irradiate
-
- else if(href_list["stealthy"])
- stealth = !stealth
-
- else if(href_list["mode"])
- scanmode += 1
- if(scanmode > 2)
- scanmode = 0
-
- else if(href_list["radint"])
- var/amount = text2num(href_list["radint"])
- amount += intensity
- intensity = max(1,(min(20,amount)))
-
- else if(href_list["radwav"])
- var/amount = text2num(href_list["radwav"])
- amount += wavelength
- wavelength = max(0,(min(120,amount)))
-
- attack_self(usr)
- add_fingerprint(usr)
- return
+ switch(action)
+ if("irradiate")
+ irradiate = !irradiate
+ . = TRUE
+ if("stealth")
+ stealth = !stealth
+ . = TRUE
+ if("scanmode")
+ scanmode = !scanmode
+ . = TRUE
+ if("radintensity")
+ var/target = params["target"]
+ var/adjust = text2num(params["adjust"])
+ if(target == "min")
+ target = 1
+ . = TRUE
+ else if(target == "max")
+ target = 20
+ . = TRUE
+ else if(adjust)
+ target = intensity + adjust
+ . = TRUE
+ else if(text2num(target) != null)
+ target = text2num(target)
+ . = TRUE
+ if(.)
+ target = round(target)
+ intensity = clamp(target, 1, 20)
+ if("radwavelength")
+ var/target = params["target"]
+ var/adjust = text2num(params["adjust"])
+ if(target == "min")
+ target = 0
+ . = TRUE
+ else if(target == "max")
+ target = 120
+ . = TRUE
+ else if(adjust)
+ target = wavelength + adjust
+ . = TRUE
+ else if(text2num(target) != null)
+ target = text2num(target)
+ . = TRUE
+ if(.)
+ target = round(target)
+ wavelength = clamp(target, 0, 120)
/obj/item/shadowcloak
name = "cloaker belt"
diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm
index 9d5b6d5b6c..32d9c02a27 100644
--- a/code/game/objects/items/devices/transfer_valve.dm
+++ b/code/game/objects/items/devices/transfer_valve.dm
@@ -13,6 +13,8 @@
var/mob/attacher = null
var/valve_open = FALSE
var/toggle = 1
+ var/ui_x = 310
+ var/ui_y = 320
/obj/item/transfer_valve/IsAssemblyHolder()
return TRUE
@@ -77,7 +79,7 @@
if(attached_device)
attached_device.Crossed(AM)
-/obj/item/transfer_valve/attack_hand()//Triggers mousetraps
+/obj/item/transfer_valve/on_attack_hand()//Triggers mousetraps
. = ..()
if(.)
return
@@ -186,11 +188,10 @@
tank_one.air_contents.merge(temp)
tank_two.air_contents.set_volume(tank_two.air_contents.return_volume() - tank_one.air_contents.return_volume())
- /*
+/*
Exadv1: I know this isn't how it's going to work, but this was just to check
it explodes properly when it gets a signal (and it does).
- */
-
+*/
/obj/item/transfer_valve/proc/toggle_valve()
if(!valve_open && tank_one && tank_two)
valve_open = TRUE
@@ -231,7 +232,60 @@
valve_open = FALSE
update_icon()
-// this doesn't do anything but the timer etc. expects it to be here
-// eventually maybe have it update icon to show state (timer, prox etc.) like old bombs
+/*
+ This doesn't do anything but the timer etc. expects it to be here
+ eventually maybe have it update icon to show state (timer, prox etc.) like old bombs
+*/
/obj/item/transfer_valve/proc/c_state()
return
+
+/obj/item/transfer_valve/ui_state(mob/user)
+ return GLOB.hands_state
+
+/obj/item/transfer_valve/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "TransferValve", name)
+ ui.open()
+
+/obj/item/transfer_valve/ui_data(mob/user)
+ var/list/data = list()
+ data["tank_one"] = tank_one
+ data["tank_two"] = tank_two
+ data["attached_device"] = attached_device
+ data["valve"] = valve_open
+ return data
+
+/obj/item/transfer_valve/ui_act(action, params)
+ if(..())
+ return
+
+ switch(action)
+ if("tankone")
+ if(tank_one)
+ split_gases()
+ valve_open = FALSE
+ tank_one.forceMove(drop_location())
+ tank_one = null
+ . = TRUE
+ if("tanktwo")
+ if(tank_two)
+ split_gases()
+ valve_open = FALSE
+ tank_two.forceMove(drop_location())
+ tank_two = null
+ . = TRUE
+ if("toggle")
+ toggle_valve()
+ . = TRUE
+ if("device")
+ if(attached_device)
+ attached_device.attack_self(usr)
+ . = TRUE
+ if("remove_device")
+ if(attached_device)
+ attached_device.on_detach()
+ attached_device = null
+ . = TRUE
+
+ update_icon()
diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm
index 4fdb862288..2fe4c67362 100644
--- a/code/game/objects/items/dice.dm
+++ b/code/game/objects/items/dice.dm
@@ -1,10 +1,10 @@
-/obj/item/storage/pill_bottle/dice
+/obj/item/storage/box/dice
name = "bag of dice"
desc = "Contains all the luck you'll ever need."
icon = 'icons/obj/dice.dmi'
icon_state = "dicebag"
-/obj/item/storage/pill_bottle/dice/Initialize()
+/obj/item/storage/box/dice/Initialize()
. = ..()
var/special_die = pick("1","2","fudge","space","00","8bd20","4dd6","100")
if(special_die == "1")
@@ -30,7 +30,7 @@
if(special_die == "100")
new /obj/item/dice/d100(src)
-/obj/item/storage/pill_bottle/dice/suicide_act(mob/user)
+/obj/item/storage/box/dice/suicide_act(mob/user)
user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!")
return (OXYLOSS)
diff --git a/code/game/objects/items/dualsaber.dm b/code/game/objects/items/dualsaber.dm
index 8c7049c713..9db9449fcb 100644
--- a/code/game/objects/items/dualsaber.dm
+++ b/code/game/objects/items/dualsaber.dm
@@ -23,6 +23,8 @@
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70)
resistance_flags = FIRE_PROOF
+ wound_bonus = -110
+ bare_wound_bonus = 20
block_parry_data = /datum/block_parry_data/dual_esword
var/hacked = FALSE
/// Can this reflect all energy projectiles?
@@ -103,7 +105,7 @@
to_chat(user, "You lack the grace to wield this!")
return COMPONENT_TWOHANDED_BLOCK_WIELD
wielded = TRUE
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
w_class = w_class_on
total_mass = total_mass_on
hitsound = 'sound/weapons/blade1.ogg'
diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm
index c4a15a1871..0d5c9a22aa 100644
--- a/code/game/objects/items/eightball.dm
+++ b/code/game/objects/items/eightball.dm
@@ -192,11 +192,13 @@
return top_vote
-/obj/item/toy/eightball/haunted/ui_interact(mob/user, ui_key="main", datum/tgui/ui=null, force_open=0, datum/tgui/master_ui=null, datum/ui_state/state = GLOB.always_state)
+/obj/item/toy/eightball/haunted/ui_state(mob/user)
+ return GLOB.observer_state
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/item/toy/eightball/haunted/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "eightball", name, 400, 600, master_ui, state)
+ ui = new(user, src, "EightBallVote", name)
ui.open()
/obj/item/toy/eightball/haunted/ui_data(mob/user)
@@ -229,4 +231,4 @@
else
votes[selected_answer] += 1
voted[user.ckey] = selected_answer
- . = TRUE
\ No newline at end of file
+ . = TRUE
diff --git a/code/game/objects/items/electrostaff.dm b/code/game/objects/items/electrostaff.dm
index 8d1fe4ebd1..9750994c87 100644
--- a/code/game/objects/items/electrostaff.dm
+++ b/code/game/objects/items/electrostaff.dm
@@ -15,6 +15,7 @@
attack_verb = list("struck", "beaten", "thwacked", "pulped")
total_mass = 5 //yeah this is a heavy thing, beating people with it while it's off is not going to do you any favors. (to curb stun-kill rampaging without it being on)
block_parry_data = /datum/block_parry_data/electrostaff
+ attack_speed = CLICK_CD_MELEE
var/obj/item/stock_parts/cell/cell = /obj/item/stock_parts/cell/high
var/on = FALSE
var/can_block_projectiles = FALSE //can't block guns
diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm
index 2d9d0b61c2..b1f51f608d 100644
--- a/code/game/objects/items/extinguisher.dm
+++ b/code/game/objects/items/extinguisher.dm
@@ -110,7 +110,7 @@
. += "The safety is [safety ? "on" : "off"]."
if(reagents.total_volume)
- . += "You can loose its screws to empty it."
+ . += "Alt-click to empty it."
/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user)
if(istype(target, tanktype) && target.Adjacent(user))
@@ -230,7 +230,7 @@
repetition++
addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection, repetition), timer_seconds)
-/obj/item/extinguisher/screwdriver_act(mob/user, obj/item/tool)
+/obj/item/extinguisher/AltClick(mob/user)
if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
EmptyExtinguisher(user)
@@ -244,7 +244,7 @@
var/turf/open/theturf = T
theturf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS)
- user.visible_message("[user] empties out \the [src] onto the floor using the release valve.", "You quietly empty out \the [src] by loosing the release valve's screws.")
+ user.visible_message("[user] empties out \the [src] onto the floor using the release valve.", "You quietly empty out \the [src] by using its release valve.")
//firebot assembly
/obj/item/extinguisher/attackby(obj/O, mob/user, params)
diff --git a/code/game/objects/items/fireaxe.dm b/code/game/objects/items/fireaxe.dm
index 41c1cbe915..6fb7b89262 100644
--- a/code/game/objects/items/fireaxe.dm
+++ b/code/game/objects/items/fireaxe.dm
@@ -13,10 +13,12 @@
slot_flags = ITEM_SLOT_BACK
attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30)
resistance_flags = FIRE_PROOF
+ wound_bonus = -15
+ bare_wound_bonus = 20
var/wielded = FALSE // track wielded status on item
/obj/item/fireaxe/Initialize()
diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm
index f293e6579a..515f5715dd 100644
--- a/code/game/objects/items/flamethrower.dm
+++ b/code/game/objects/items/flamethrower.dm
@@ -131,6 +131,7 @@
/obj/item/flamethrower/analyzer_act(mob/living/user, obj/item/I)
if(ptank)
ptank.analyzer_act(user, I)
+ return TRUE
/obj/item/flamethrower/attack_self(mob/user)
diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm
index 4ad71f7c46..609cc3b719 100644
--- a/code/game/objects/items/granters.dm
+++ b/code/game/objects/items/granters.dm
@@ -79,7 +79,7 @@
ADD_TRAIT(user, granted_trait, BOOK_TRAIT)
/obj/item/book/granter/trait/rifleman
- name = "\proper the Neo-Russian Rifleman\'s Primer"
+ name = "The Neo-Russian Rifleman\'s Primer"
desc = "A book with stains of vodka and...blood? The back is hard to read, but says something about bolt-actions. Or pump-actions. Both, maybe."
oneuse = FALSE
granted_trait = TRAIT_FAST_PUMP
@@ -477,6 +477,23 @@
name = "empty scroll"
icon_state = "blankscroll"
+/obj/item/book/granter/martial/krav_maga
+ martial = /datum/martial_art/krav_maga
+ name = "parchment scroll"
+ martialname = "krav maga"
+ desc = "A worn parchment scrap written in an ancient language. Somehow you can still understand the lessons!"
+ greet = "You have learned the ancient martial art of Krav Maga. You have special attacks with which to take down your foes."
+ icon = 'icons/obj/wizard.dmi'
+ icon_state ="scroll2"
+ remarks = list("Sweep the legs...", "Chop the throat...", "Punch the lungs...", "Get the gold...", "Where are my sick gloves..?")
+
+/obj/item/book/granter/martial/krav_maga/onlearned(mob/living/carbon/user)
+ . = ..()
+ if(oneuse == TRUE)
+ desc = "It's completely blank."
+ name = "empty scroll"
+ icon_state = "blankscroll"
+
// I did not include mushpunch's grant, it is not a book and the item does it just fine.
diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm
index f51db9fa4c..bf9fab5f04 100644
--- a/code/game/objects/items/grenades/flashbang.dm
+++ b/code/game/objects/items/grenades/flashbang.dm
@@ -60,6 +60,14 @@
shrapnel_type = /obj/item/projectile/bullet/pellet/stingball/mega
shrapnel_radius = 12
+/obj/item/grenade/stingbang/breaker
+ name = "breakbang"
+ shrapnel_type = /obj/item/projectile/bullet/pellet/stingball/breaker
+
+/obj/item/grenade/stingbang/shred
+ name = "shredbang"
+ shrapnel_type = /obj/item/projectile/bullet/pellet/stingball/shred
+
/obj/item/grenade/stingbang/prime(mob/living/lanced_by)
if(iscarbon(loc))
var/mob/living/carbon/C = loc
@@ -116,9 +124,11 @@
/obj/item/grenade/primer/attack_self(mob/user)
. = ..()
if(active)
+ if(!user.CheckActionCooldown())
+ return
user.playsound_local(user, 'sound/misc/box_deploy.ogg', 50, TRUE)
rots++
- user.changeNext_move(CLICK_CD_RAPID)
+ user.DelayNextAction(CLICK_CD_RAPID)
/obj/item/grenade/primer/prime(mob/living/lanced_by)
shrapnel_radius = round(rots / rots_per_mag)
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index c7c9fa37a9..4c9ea06620 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -320,7 +320,7 @@
do_sparks(1, TRUE, src)
qdel(src)
-/obj/item/restraints/legcuffs/beartrap/energy/attack_hand(mob/user)
+/obj/item/restraints/legcuffs/beartrap/energy/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
Crossed(user) //honk
. = ..()
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index d6396f9902..58146be20f 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -225,6 +225,7 @@
throwforce = 10
w_class = WEIGHT_CLASS_TINY
obj_flags = UNIQUE_RENAME
+ wound_bonus = -10
var/chaplain_spawnable = TRUE
total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
@@ -351,7 +352,7 @@
w_class = WEIGHT_CLASS_HUGE
slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT
block_chance = 30
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
@@ -451,7 +452,7 @@
w_class = WEIGHT_CLASS_BULKY
armour_penetration = 35
slot_flags = ITEM_SLOT_BACK
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
attack_verb = list("chopped", "sliced", "cut", "reaped")
/obj/item/nullrod/scythe/Initialize()
@@ -574,7 +575,7 @@
righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi'
w_class = WEIGHT_CLASS_HUGE
item_flags = ABSTRACT
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
attack_verb = list("sawed", "torn", "cut", "chopped", "diced")
hitsound = 'sound/weapons/chainsawhit.ogg'
total_mass = TOTAL_MASS_HAND_REPLACEMENT
@@ -593,7 +594,7 @@
name = "clown dagger"
desc = "Used for absolutely hilarious sacrifices."
hitsound = 'sound/items/bikehorn.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
/obj/item/nullrod/pride_hammer
@@ -644,7 +645,7 @@
throw_speed = 4
throw_range = 7
throwforce = 30
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
attack_verb = list("enlightened", "redpilled")
/obj/item/nullrod/armblade
@@ -656,7 +657,9 @@
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
item_flags = ABSTRACT
w_class = WEIGHT_CLASS_HUGE
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
+ wound_bonus = -20
+ bare_wound_bonus = 25
total_mass = TOTAL_MASS_HAND_REPLACEMENT
/obj/item/nullrod/armblade/Initialize()
@@ -696,7 +699,7 @@
force = 15
block_chance = 40
slot_flags = ITEM_SLOT_BACK
- sharpness = IS_BLUNT
+ sharpness = SHARP_NONE
hitsound = "swing_hit"
attack_verb = list("smashed", "slammed", "whacked", "thwacked")
icon = 'icons/obj/items_and_weapons.dmi'
@@ -751,7 +754,7 @@
name = "arrhythmic knife"
w_class = WEIGHT_CLASS_HUGE
desc = "They say fear is the true mind killer, but stabbing them in the head works too. Honour compels you to not sheathe it once drawn."
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
slot_flags = null
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
@@ -779,7 +782,7 @@
desc = "Holding this makes you look absolutely devilish."
attack_verb = list("poked", "impaled", "pierced", "jabbed")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/nullrod/egyptian
name = "egyptian staff"
diff --git a/code/game/objects/items/implants/implant_misc.dm b/code/game/objects/items/implants/implant_misc.dm
index 6cdc5ee824..08d6a586c8 100644
--- a/code/game/objects/items/implants/implant_misc.dm
+++ b/code/game/objects/items/implants/implant_misc.dm
@@ -38,6 +38,23 @@
if(!uses)
qdel(src)
+/obj/item/implant/warp
+ name = "warp implant"
+ desc = "Saves your position somewhere, and then warps you back to it after five seconds."
+ icon_state = "warp"
+ uses = 15
+
+/obj/item/implant/warp/activate()
+ . = ..()
+ uses--
+ imp_in.do_adrenaline(20, TRUE, 0, 0, TRUE, list(/datum/reagent/fermi/eigenstate = 1.2), "You feel an internal prick as as the bluespace starts ramping up!")
+ to_chat(imp_in, "You feel an internal prick as as the bluespace starts ramping up!")
+ if(!uses)
+ qdel(src)
+
+/obj/item/implanter/warp
+ name = "implanter (warp)"
+ imp_type = /obj/item/implant/warp
/obj/item/implant/emp
name = "emp implant"
@@ -69,4 +86,4 @@
healthstring = "Oxygen Deprivation Damage => [round(L.getOxyLoss())] Fire Damage => [round(L.getFireLoss())] Toxin Damage => [round(L.getToxLoss())] Brute Force Damage => [round(L.getBruteLoss())]"
if (!healthstring)
healthstring = "ERROR"
- return healthstring
\ No newline at end of file
+ return healthstring
diff --git a/code/game/objects/items/implants/implant_radio.dm b/code/game/objects/items/implants/implant_radio.dm
index 5d3d579a4e..6f42547004 100644
--- a/code/game/objects/items/implants/implant_radio.dm
+++ b/code/game/objects/items/implants/implant_radio.dm
@@ -10,7 +10,7 @@
/obj/item/implant/radio/activate()
. = ..()
// needs to be GLOB.deep_inventory_state otherwise it won't open
- radio.ui_interact(usr, "main", null, FALSE, null, GLOB.deep_inventory_state)
+ radio.ui_interact(usr, state = GLOB.deep_inventory_state)
/obj/item/implant/radio/implant(mob/living/target, mob/user, silent = FALSE)
. = ..()
diff --git a/code/game/objects/items/implants/implantchair.dm b/code/game/objects/items/implants/implantchair.dm
index 3ea27c84bb..7b05cf302e 100644
--- a/code/game/objects/items/implants/implantchair.dm
+++ b/code/game/objects/items/implants/implantchair.dm
@@ -26,14 +26,15 @@
open_machine()
update_icon()
+/obj/machinery/implantchair/ui_state(mob/user)
+ return GLOB.notcontained_state
-/obj/machinery/implantchair/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/implantchair/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "implantchair", name, 375, 280, master_ui, state)
+ ui = new(user, src, "ImplantChair", name)
ui.open()
-
/obj/machinery/implantchair/ui_data()
var/list/data = list()
data["occupied"] = occupant ? 1 : 0
@@ -121,8 +122,6 @@
update_icon()
/obj/machinery/implantchair/container_resist(mob/living/user)
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message("You see [user] kicking against the door of [src]!", \
"You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \
"You hear a metallic creaking from [src].")
@@ -191,4 +190,4 @@
brainwash(C, objective)
message_admins("[ADMIN_LOOKUPFLW(user)] brainwashed [key_name_admin(C)] with objective '[objective]'.")
log_game("[key_name(user)] brainwashed [key_name(C)] with objective '[objective]'.")
- return TRUE
\ No newline at end of file
+ return TRUE
diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm
index dda41494ff..e7cf8defc9 100644
--- a/code/game/objects/items/kitchen.dm
+++ b/code/game/objects/items/kitchen.dm
@@ -18,7 +18,7 @@
name = "fork"
desc = "Pointy."
icon_state = "fork"
- force = 5
+ force = 4
w_class = WEIGHT_CLASS_TINY
throwforce = 0
throw_speed = 3
@@ -28,6 +28,7 @@
attack_verb = list("attacked", "stabbed", "poked")
hitsound = 'sound/weapons/bladeslice.ogg'
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ sharpness = SHARP_POINTY
var/datum/reagent/forkload //used to eat omelette
/obj/item/kitchen/fork/suicide_act(mob/living/carbon/user)
@@ -54,6 +55,14 @@
else
return ..()
+/obj/item/kitchen/fork/throwing
+ name = "throwing fork"
+ desc = "A fork, sharpened to perfection, making it a great weapon for throwing."
+ throwforce = 15
+ throw_speed = 4
+ throw_range = 6
+ embedding = list("pain_mult" = 2, "embed_chance" = 100, "fall_chance" = 0, "embed_chance_turf_mod" = 15)
+ sharpness = SHARP_EDGED
/obj/item/kitchen/knife
name = "kitchen knife"
@@ -68,9 +77,11 @@
throw_range = 6
custom_materials = list(/datum/material/iron=12000)
attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- sharpness = IS_SHARP_ACCURATE
+ sharpness = SHARP_POINTY
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
var/bayonet = FALSE //Can this be attached to a gun?
+ wound_bonus = -5
+ bare_wound_bonus = 10
custom_price = PRICE_NORMAL
/obj/item/kitchen/knife/Initialize()
diff --git a/code/game/objects/items/latexballoon.dm b/code/game/objects/items/latexballoon.dm
index ef5b7b6cba..b2f8b4bee2 100644
--- a/code/game/objects/items/latexballoon.dm
+++ b/code/game/objects/items/latexballoon.dm
@@ -54,5 +54,5 @@
var/obj/item/tank/T = W
blow(T, user)
return
- if (W.get_sharpness() || W.get_temperature() || is_pointed(W))
+ if (W.get_sharpness() || W.get_temperature())
burst()
diff --git a/code/game/objects/items/manuals.dm b/code/game/objects/items/manuals.dm
index 6c01e68a48..0673e1d489 100644
--- a/code/game/objects/items/manuals.dm
+++ b/code/game/objects/items/manuals.dm
@@ -506,7 +506,9 @@
if(prob(50))
step(W, pick(GLOB.alldirs))
ADD_TRAIT(H, TRAIT_DISFIGURED, TRAIT_GENERIC)
- H.bleed_rate = 5
+ for(var/i in H.bodyparts)
+ var/obj/item/bodypart/BP = i
+ BP.generic_bleedstacks += 5
H.gib_animation()
sleep(3)
H.adjustBruteLoss(1000) //to make the body super-bloody
diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm
index 20960da7c6..679491aeb6 100644
--- a/code/game/objects/items/melee/energy.dm
+++ b/code/game/objects/items/melee/energy.dm
@@ -102,7 +102,7 @@
attack_verb_off = list("tapped", "poked")
throw_speed = 3
throw_range = 5
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
embedding = list("embed_chance" = 75, "impact_pain_mult" = 10)
armour_penetration = 35
item_flags = NEEDS_PERMIT | ITEM_CAN_PARRY
@@ -147,6 +147,12 @@
return NONE
return ..()
+/obj/item/melee/transforming/energy/sword/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time)
+ . = ..()
+ if(parry_efficiency >= 80) // perfect parry
+ block_return[BLOCK_RETURN_REDIRECT_METHOD] = REDIRECT_METHOD_RETURN_TO_SENDER
+ . |= BLOCK_SHOULD_REDIRECT
+
/obj/item/melee/transforming/energy/sword/cyborg
sword_color = "red"
light_color = "#ff0000"
@@ -174,7 +180,7 @@
sword_color = null //stops icon from breaking when turned on.
hitcost = 75 //Costs more than a standard cyborg esword
w_class = WEIGHT_CLASS_NORMAL
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
light_color = "#40ceff"
tool_behaviour = TOOL_SAW
toolspeed = 0.7
@@ -249,7 +255,7 @@
throw_range = 1
w_class = WEIGHT_CLASS_BULKY//So you can't hide it in your pocket or some such.
var/datum/effect_system/spark_spread/spark_system
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
//Most of the other special functions are handled in their own files. aka special snowflake code so kewl
/obj/item/melee/transforming/energy/blade/Initialize()
@@ -285,7 +291,7 @@
attack_verb_off = list("tapped", "poked")
throw_speed = 3
throw_range = 5
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
embedding = list("embedded_pain_multiplier" = 6, "embed_chance" = 20, "embedded_fall_chance" = 60)
armour_penetration = 10
block_chance = 35
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index c6aa9f7bf4..01d2924a90 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -19,6 +19,8 @@
slot_flags = ITEM_SLOT_BELT
force = 14
throwforce = 10
+ wound_bonus = 15
+ bare_wound_bonus = 10
reach = 2
w_class = WEIGHT_CLASS_NORMAL
attack_verb = list("flogged", "whipped", "lashed", "disciplined")
@@ -42,7 +44,7 @@
throwforce = 10
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "impaled", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
total_mass = TOTAL_MASS_HAND_REPLACEMENT
/obj/item/melee/synthetic_arm_blade/Initialize()
@@ -62,7 +64,7 @@
throwforce = 15
w_class = WEIGHT_CLASS_BULKY
armour_penetration = 75
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
attack_verb = list("slashed", "cut")
hitsound = 'sound/weapons/rapierhit.ogg'
custom_materials = list(/datum/material/iron = 1000)
@@ -166,7 +168,7 @@
flags_1 = CONDUCT_1
obj_flags = UNIQUE_RENAME
w_class = WEIGHT_CLASS_BULKY
- sharpness = IS_SHARP_ACCURATE //It cant be sharpend cook -_-
+ sharpness = SHARP_POINTY //It cant be sharpend cook -_-
attack_verb = list("stabs", "punctures", "pierces", "pokes")
hitsound = 'sound/weapons/rapierhit.ogg'
total_mass = 0.4
@@ -268,6 +270,8 @@
var/force_off // Damage when off - not stunning
var/weight_class_on // What is the new size class when turned on
+ wound_bonus = 15
+
/obj/item/melee/classic_baton/Initialize()
. = ..()
@@ -371,6 +375,7 @@
var/wait_desc = get_wait_description()
if(wait_desc)
to_chat(user, wait_desc)
+ return DISCARD_LAST_ACTION
/obj/item/melee/classic_baton/telescopic
name = "telescopic baton"
@@ -393,6 +398,7 @@
force_off = 0
weight_class_on = WEIGHT_CLASS_BULKY
total_mass = TOTAL_MASS_NORMAL_ITEM
+ bare_wound_bonus = 5
/obj/item/melee/classic_baton/telescopic/suicide_act(mob/user)
var/mob/living/carbon/human/H = user
@@ -621,7 +627,7 @@
to_chat(user, "[target] doesn't seem to want to get on [src]!")
update_icon()
-/obj/item/melee/roastingstick/attack_hand(mob/user)
+/obj/item/melee/roastingstick/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
..()
if (held_sausage)
user.put_in_hands(held_sausage)
diff --git a/code/game/objects/items/melee/transforming.dm b/code/game/objects/items/melee/transforming.dm
index 386a6e9acc..8c44a15cd4 100644
--- a/code/game/objects/items/melee/transforming.dm
+++ b/code/game/objects/items/melee/transforming.dm
@@ -1,5 +1,5 @@
/obj/item/melee/transforming
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
var/active = FALSE
var/force_on = 30 //force when active
var/faction_bonus_force = 0 //Bonus force dealt against certain factions
@@ -84,4 +84,4 @@
/obj/item/melee/transforming/proc/clumsy_transform_effect(mob/living/user)
if(clumsy_check && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
to_chat(user, "You accidentally cut yourself with [src], like a doofus!")
- user.take_bodypart_damage(5,5)
\ No newline at end of file
+ user.take_bodypart_damage(5,5)
diff --git a/code/game/objects/items/miscellaneous.dm b/code/game/objects/items/miscellaneous.dm
index 7237a1788f..a7f7e3152c 100644
--- a/code/game/objects/items/miscellaneous.dm
+++ b/code/game/objects/items/miscellaneous.dm
@@ -18,6 +18,8 @@
icon = 'icons/obj/device.dmi'
icon_state = "gangtool-blue"
item_state = "radio"
+ var/list/stored_options
+ var/force_refresh = FALSE //if set to true, the beacon will recalculate its display options whenever opened
/obj/item/choice_beacon/attack_self(mob/user)
if(canUseBeacon(user))
@@ -34,14 +36,15 @@
return FALSE
/obj/item/choice_beacon/proc/generate_options(mob/living/M)
- var/list/display_names = generate_display_names()
- if(!display_names.len)
+ if(!stored_options || force_refresh)
+ stored_options = generate_display_names()
+ if(!stored_options.len)
return
- var/choice = input(M,"Which item would you like to order?","Select an Item") as null|anything in display_names
+ var/choice = input(M,"Which item would you like to order?","Select an Item") as null|anything in stored_options
if(!choice || !M.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
- spawn_option(display_names[choice],M)
+ spawn_option(stored_options[choice],M)
qdel(src)
/obj/item/choice_beacon/proc/spawn_option(obj/choice,mob/living/M)
@@ -153,6 +156,51 @@
new choice(get_turf(M))
to_chat(M, "You hear something crackle from the beacon for a moment before a voice speaks. \"Please stand by for a message from S.E.L.F. Message as follows: Item request received. Your package has been transported, use the autosurgeon supplied to apply the upgrade. Message ends.\"")
+/obj/item/choice_beacon/box
+ name = "choice box (default)"
+ desc = "Think really hard about what you want, and then rip it open!"
+ icon = 'icons/obj/storage.dmi'
+ icon_state = "deliverypackage3"
+ item_state = "deliverypackage3"
+
+/obj/item/choice_beacon/box/spawn_option(obj/choice,mob/living/M)
+ to_chat(M, "The box opens, revealing the [choice]!")
+ playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ M.temporarilyRemoveItemFromInventory(src, TRUE)
+ M.put_in_hands(new choice)
+ qdel(src)
+
+/obj/item/choice_beacon/box/plushie
+ name = "choice box (plushie)"
+ desc = "Using the power of quantum entanglement, this box contains every plush, until the moment it is opened!"
+ icon = 'icons/obj/plushes.dmi'
+ icon_state = "box"
+ item_state = "box"
+
+/obj/item/choice_beacon/box/spawn_option(choice,mob/living/M)
+ if(ispath(choice, /obj/item/toy/plush))
+ ..() //regular plush, spawn it naturally
+ else
+ //snowflake plush
+ var/obj/item/toy/plush/snowflake_plushie = new(get_turf(M))
+ snowflake_plushie.set_snowflake_from_config(choice)
+ M.temporarilyRemoveItemFromInventory(src, TRUE)
+ M.put_in_hands(new choice)
+ qdel(src)
+
+/obj/item/choice_beacon/box/plushie/generate_display_names()
+ var/list/plushie_list = list()
+ //plushie set 1: just subtypes of /obj/item/toy/plush
+ var/list/plushies_set_one = subtypesof(/obj/item/toy/plush) - list(/obj/item/toy/plush/narplush, /obj/item/toy/plush/awakenedplushie, /obj/item/toy/plush/random_snowflake, /obj/item/toy/plush/random) //don't allow these special ones (you can still get narplush/hugbox)
+ for(var/V in plushies_set_one)
+ var/atom/A = V
+ plushie_list[initial(A.name)] = A
+ //plushie set 2: snowflake plushies
+ var/list/plushies_set_two = CONFIG_GET(keyed_list/snowflake_plushies)
+ for(var/V in plushies_set_two)
+ plushie_list[V] = V //easiest way to do this which works with how selecting options works, despite being snowflakey to have the key equal the value
+ return plushie_list
+
/obj/item/skub
desc = "It's skub."
name = "skub"
diff --git a/code/game/objects/items/mop.dm b/code/game/objects/items/mop.dm
index 3a06c7d7fe..b420bfc002 100644
--- a/code/game/objects/items/mop.dm
+++ b/code/game/objects/items/mop.dm
@@ -58,7 +58,7 @@
if(T)
user.visible_message("[user] cleans \the [T] with [src].", "You clean \the [T] with [src].")
clean(T)
- user.changeNext_move(CLICK_CD_MELEE)
+ user.DelayNextAction(CLICK_CD_MELEE)
user.do_attack_animation(T, used_item = src)
if(istype(L))
L.adjustStaminaLossBuffered(stamusage)
@@ -128,4 +128,4 @@
return ..()
/obj/item/mop/advanced/cyborg
- insertable = FALSE
\ No newline at end of file
+ insertable = FALSE
diff --git a/code/game/objects/items/pet_carrier.dm b/code/game/objects/items/pet_carrier.dm
index 8b322d20cb..c6b5a7e224 100644
--- a/code/game/objects/items/pet_carrier.dm
+++ b/code/game/objects/items/pet_carrier.dm
@@ -24,6 +24,7 @@
var/max_occupant_weight = MOB_SIZE_SMALL //This is calculated from the mob sizes of occupants
var/entrance_name = "door" //name of the entrance to the item
var/escape_time = 200 //how long it takes for mobs above small sizes to escape (for small sizes, its randomly 1.5 to 2x this)
+ var/alternate_escape_time = 0 //how long it takes for mobs to escape when the entrance is open
var/load_time = 30 //how long it takes for mobs to be loaded into the pet carrier
var/has_lock_sprites = TRUE //whether to load the lock overlays or not
var/allows_hostiles = FALSE //does the pet carrier allow hostile entities to be held within it?
@@ -106,8 +107,9 @@
if(user == target)
to_chat(user, "Why would you ever do that?")
return
- if(ishostile(target) && !allows_hostiles && target.move_resist < MOVE_FORCE_VERY_STRONG) //don't allow goliaths into pet carriers
+ if((ishostile(target) && (!allows_hostiles || !istype(target, /mob/living/simple_animal/hostile/carp/cayenne))) || target.move_resist >= MOVE_FORCE_VERY_STRONG) //don't allow goliaths into pet carriers, but let cayenne in!
to_chat(user, "You have a feeling you shouldn't keep this as a pet.")
+ return
load_occupant(user, target)
/obj/item/pet_carrier/relaymove(mob/living/user, direction)
@@ -128,13 +130,18 @@
/obj/item/pet_carrier/container_resist(mob/living/user)
//don't do the whole resist timer thing if it's open!
if(open)
- loc.visible_message("[user] climbs out of [src]!", \
- "[user] jumps out of [src]!")
- remove_occupant(user)
- return
+ if(alternate_escape_time > 0)
+ loc.visible_message("The [src] begins to shake!")
+ if(do_after(user, alternate_escape_time, target = user))
+ loc.visible_message("[user] jumps out of [src]")
+ remove_occupant(user)
+ return
+ else //instant escape, different message
+ loc.visible_message("[user] climbs out of [src]!", \
+ "[user] jumps out of [src]!")
+ remove_occupant(user)
+ return
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
if(user.mob_size <= MOB_SIZE_SMALL)
to_chat(user, "You begin to try escaping the [src] and start fumbling for the lock switch... (This will take some time.)")
to_chat(loc, "You see [user] attempting to unlock the [src]!")
@@ -147,7 +154,7 @@
update_icon()
else
loc.visible_message("[src] starts rattling as something pushes against the [entrance_name]!", null, null, null, user)
- to_chat(user, "You start pushing out of [src]... (This will take about 20 seconds.)")
+ to_chat(user, "You start pushing out of [src]... (This will take about [escape_time/10] seconds.)")
if(!do_after(user, escape_time, target = user) || open || !locked || !(user in occupants))
return
loc.visible_message("[user] shoves out of [src]!", null, null, null, user)
@@ -180,21 +187,22 @@
/obj/item/pet_carrier/proc/load_occupant(mob/living/user, mob/living/target)
if(pet_carrier_full(src))
to_chat(user, "[src] is already carrying too much!")
- return
+ return FALSE
user.visible_message("[user] starts loading [target] into [src].", \
"You start loading [target] into [src]...", null, null, target)
to_chat(target, "[user] starts loading you into [user.p_their()] [name]!")
if(!do_mob(user, target, load_time))
- return
+ return FALSE
if(target in occupants)
- return
+ return FALSE
if(pet_carrier_full(src)) //Run the checks again, just in case
to_chat(user, "[src] is already carrying too much!")
- return
+ return FALSE
user.visible_message("[user] loads [target] into [src]!", \
"You load [target] into [src].", null, null, target)
to_chat(target, "[user] loads you into [user.p_their()] [name]!")
add_occupant(target)
+ return TRUE
/obj/item/pet_carrier/proc/add_occupant(mob/living/occupant)
if(occupant in occupants || !istype(occupant))
@@ -230,7 +238,8 @@
allows_hostiles = TRUE //can fit hostile creatures, with the move resist restrictions in place, this means they still cannot take things like legions/goliaths/etc regardless
has_lock_sprites = FALSE //jar doesn't show the regular lock overlay
custom_materials = list(/datum/material/glass = 1000, /datum/material/bluespace = 600)
- escape_time = 10 //half the time of a bluespace bodybag
+ escape_time = 200 //equal to the time of a bluespace bodybag
+ alternate_escape_time = 100
var/datum/gas_mixture/occupant_gas_supply
/obj/item/pet_carrier/bluespace/update_icon_state()
@@ -253,7 +262,8 @@
occupant_gas_supply = new
if(isanimal(occupant))
var/mob/living/simple_animal/animal = occupant
- occupant_gas_supply.set_temperature(animal.minbodytemp) //simple animals only care about temperature when their turf isnt a location
+ occupant_gas_supply[/datum/gas/oxygen] = 0.0064 //make sure it has some gas in so it isn't depressurized
+ occupant_gas_supply.set_temperature(animal.minbodytemp) //simple animals only care about temperature/pressure when their turf isnt a location
else
if(ishuman(occupant)) //humans require resistance to cold/heat and living in no air while inside, and lose this when outside
ADD_TRAIT(occupant, TRAIT_RESISTCOLD, "bluespace_container_cold_resist")
@@ -270,10 +280,15 @@
REMOVE_TRAIT(occupant, TRAIT_NOBREATH, "bluespace_container_no_breath")
REMOVE_TRAIT(occupant, TRAIT_RESISTHIGHPRESSURE, "bluespace_container_resist_high_pressure")
REMOVE_TRAIT(occupant, TRAIT_RESISTLOWPRESSURE, "bluespace_container_resist_low_pressure")
+ name = initial(name)
/obj/item/pet_carrier/bluespace/return_air()
if(!occupant_gas_supply)
occupant_gas_supply = new
return occupant_gas_supply
+/obj/item/pet_carrier/bluespace/load_occupant(mob/living/user, mob/living/target)
+ if(..())
+ name = "[initial(name)] ([target])"
+
#undef pet_carrier_full
diff --git a/code/game/objects/items/pitchfork.dm b/code/game/objects/items/pitchfork.dm
index 49d0b64498..b296e2d0cb 100644
--- a/code/game/objects/items/pitchfork.dm
+++ b/code/game/objects/items/pitchfork.dm
@@ -9,7 +9,7 @@
w_class = WEIGHT_CLASS_BULKY
attack_verb = list("attacked", "impaled", "pierced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30)
resistance_flags = FIRE_PROOF
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index 70426eac1c..a3da49ed89 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -8,6 +8,7 @@
resistance_flags = FLAMMABLE
var/list/squeak_override //Weighted list; If you want your plush to have different squeak sounds use this
var/stuffed = TRUE //If the plushie has stuffing in it
+ var/unstuffable = FALSE //for plushies that can't be stuffed
var/obj/item/grenade/grenade //You can remove the stuffing from a plushie and add a grenade to it for *nefarious uses*
//--love ~<3--
gender = NEUTER
@@ -174,6 +175,9 @@
/obj/item/toy/plush/attackby(obj/item/I, mob/living/user, params)
if(I.get_sharpness())
if(!grenade)
+ if(unstuffable)
+ to_chat(user, "Nothing to do here.")
+ return
if(!stuffed)
to_chat(user, "You already murdered it!")
return
@@ -187,6 +191,13 @@
grenade = null
return
if(istype(I, /obj/item/grenade))
+ if(unstuffable)
+ to_chat(user, "No... you should destroy it now!")
+ sleep(10)
+ if(QDELETED(user) || QDELETED(src))
+ return
+ SEND_SOUND(user, 'sound/weapons/armbomb.ogg')
+ return
if(stuffed)
to_chat(user, "You need to remove some stuffing first!")
return
@@ -755,3 +766,14 @@ GLOBAL_LIST_INIT(valid_plushie_paths, valid_plushie_paths())
attack_verb = list("headbutt", "scritched", "bit")
squeak_override = list('modular_citadel/sound/voice/nya.ogg' = 1)
can_random_spawn = FALSE
+
+
+/obj/item/toy/plush/hairball
+ name = "Hairball"
+ desc = "A bundle of undigested fibers and scales. Yuck."
+ icon_state = "Hairball"
+ unstuffable = TRUE
+ young = TRUE // Your own mouth-baby.
+ squeak_override = list('sound/misc/splort.ogg'=1)
+ attack_verb = list("sploshed", "splorted", "slushed")
+ can_random_spawn = FALSE
diff --git a/code/game/objects/items/powerfist.dm b/code/game/objects/items/powerfist.dm
index b7e2d22d2f..2834b3b758 100644
--- a/code/game/objects/items/powerfist.dm
+++ b/code/game/objects/items/powerfist.dm
@@ -14,12 +14,11 @@
w_class = WEIGHT_CLASS_NORMAL
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 40)
resistance_flags = FIRE_PROOF
- click_delay = CLICK_CD_MELEE * 1.5
+ attack_speed = CLICK_CD_MELEE * 1.5
var/fisto_setting = 1
var/gasperfist = 3
var/obj/item/tank/internals/tank = null //Tank used for the gauntlet's piston-ram.
-
/obj/item/melee/powerfist/examine(mob/user)
. = ..()
if(!in_range(user, src))
@@ -98,7 +97,7 @@
target.visible_message("[user]'s powerfist lets out a weak hiss as [user.p_they()] punch[user.p_es()] [target.name]!", \
"[user]'s punch strikes with force!")
return
- target.apply_damage(totalitemdamage * fisto_setting, BRUTE)
+ target.apply_damage(totalitemdamage * fisto_setting, BRUTE, wound_bonus = -25*fisto_setting**2)
target.visible_message("[user]'s powerfist lets out a loud hiss as [user.p_they()] punch[user.p_es()] [target.name]!", \
"You cry out in pain as [user]'s punch flings you backwards!")
new /obj/effect/temp_visual/kinetic_blast(target.loc)
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index 1f69cdae2e..6513b53e1d 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -746,8 +746,8 @@
***********************************************************************/
/obj/item/weapon/gripper
- name = "circuit gripper"
- desc = "A simple grasping tool for inserting circuitboards into machinary."
+ name = "engineering gripper"
+ desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more. Alt click to drop instead of use."
icon = 'icons/obj/device.dmi'
icon_state = "gripper"
@@ -755,18 +755,36 @@
//Has a list of items that it can hold.
var/list/can_hold = list(
- /obj/item/circuitboard
+ /obj/item/circuitboard,
+ /obj/item/light,
+ /obj/item/electronics,
+ /obj/item/tank,
+ /obj/item/conveyor_switch_construct,
+ /obj/item/stack/conveyor,
+ /obj/item/wallframe,
+ /obj/item/vending_refill,
+ /obj/item/stack/sheet,
+ /obj/item/stack/tile,
+ /obj/item/stack/rods,
+ /obj/item/stock_parts
+ )
+ //Basically a blacklist for any subtypes above we dont want
+ var/list/cannot_hold = list(
+ /obj/item/stack/sheet/mineral/plasma,
+ /obj/item/stack/sheet/plasteel
)
var/obj/item/wrapped = null // Item currently being held.
-/obj/item/weapon/gripper/attack_self()
+//Used to interact with UI's of held items, such as gas tanks and airlock electronics.
+/obj/item/weapon/gripper/AltClick(mob/user)
if(wrapped)
wrapped.forceMove(get_turf(wrapped))
+ to_chat(user, "You drop the [wrapped].")
wrapped = null
return ..()
-/obj/item/weapon/gripper/afterattack(var/atom/target, var/mob/living/user, proximity, params)
+/obj/item/weapon/gripper/pre_attack(var/atom/target, var/mob/living/silicon/robot/user, proximity, params)
if(!proximity)
return
@@ -792,18 +810,21 @@
return
else if(istype(target,/obj/item))
-
var/obj/item/I = target
-
var/grab = 0
+
for(var/typepath in can_hold)
if(istype(I,typepath))
grab = 1
- break
+ for(var/badpath in cannot_hold)
+ if(istype(I,badpath))
+ if(!user.emagged)
+ grab = 0
+ continue
//We can grab the item, finally.
if(grab)
- to_chat(user, "You collect \the [I].")
+ to_chat(user, "You collect \the [I].")
I.loc = src
wrapped = I
return
@@ -812,18 +833,24 @@
/obj/item/weapon/gripper/mining
name = "shelter capsule deployer"
- desc = "A simple grasping tool for carrying and deploying shelter capsules."
+ desc = "A simple grasping tool for carrying and deploying shelter capsules. Alt click to drop instead of use."
icon_state = "gripper_mining"
can_hold = list(
/obj/item/survivalcapsule
)
-/obj/item/weapon/gripper/mining/attack_self()
- if(wrapped)
- wrapped.forceMove(get_turf(wrapped))
- wrapped.attack_self()
- wrapped = null
- return
+/obj/item/weapon/gripper/medical
+ name = "medical gripper"
+ desc = "A simple grasping tool for interacting with medical equipment, such as beakers, blood bags, chem bags and more. Alt click to drop instead of use."
+ icon_state = "gripper_medical"
+ can_hold = list(
+ /obj/item/storage/bag/bio,
+ /obj/item/storage/bag/chemistry,
+ /obj/item/storage/pill_bottle,
+ /obj/item/reagent_containers/glass,
+ /obj/item/reagent_containers/pill,
+ /obj/item/reagent_containers/blood
+ )
/obj/item/gun/energy/plasmacutter/cyborg
name = "cyborg plasma cutter"
@@ -907,6 +934,9 @@
icon_state = "data_1"
+/**********************************************************************
+ Dogborg stuff
+***********************************************************************/
///Mere cosmetic dogborg items, remnants of what were once the most annoying cyborg modules.
/obj/item/dogborg_tongue
name = "synthetic tongue"
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index d33ecedf0a..80fd177d84 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -6,21 +6,24 @@
desc = "Protected by FRM."
icon = 'icons/obj/module.dmi'
icon_state = "cyborg_upgrade"
+ w_class = WEIGHT_CLASS_SMALL
var/locked = FALSE
var/installed = 0
var/require_module = 0
var/list/module_type
+ /// Bitflags listing module compatibility. Used in the exosuit fabricator for creating sub-categories.
+ var/module_flags = NONE
// if true, is not stored in the robot to be ejected
// if module is reset
var/one_use = FALSE
/obj/item/borg/upgrade/proc/action(mob/living/silicon/robot/R, user = usr)
if(R.stat == DEAD)
- to_chat(user, "[src] will not function on a deceased cyborg.")
+ to_chat(user, "[src] will not function on a deceased cyborg.")
return FALSE
if(module_type && !is_type_in_list(R.module, module_type))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(user, "There's no mounting point for the module!")
+ to_chat(R, "Upgrade mounting error! No suitable hardpoint detected.")
+ to_chat(user, "There's no mounting point for the module!")
return FALSE
return TRUE
@@ -37,7 +40,7 @@
one_use = TRUE
/obj/item/borg/upgrade/rename/attack_self(mob/user)
- heldname = stripped_input(user, "Enter new robot name", "Cyborg Reclassification", heldname, MAX_NAME_LEN)
+ heldname = sanitize_name(stripped_input(user, "Enter new robot name", "Cyborg Reclassification", heldname, MAX_NAME_LEN))
/obj/item/borg/upgrade/rename/action(mob/living/silicon/robot/R)
. = ..()
@@ -95,6 +98,7 @@
desc = "Used to cool a mounted energy-based firearm, increasing the potential current in it and thus its recharge rate."
icon_state = "cyborg_upgrade3"
require_module = 1
+ module_flags = BORG_MODULE_SECURITY
/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -147,6 +151,7 @@
icon_state = "cyborg_upgrade3"
require_module = 1
module_type = list(/obj/item/robot_module/miner)
+ module_flags = BORG_MODULE_MINER
/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -207,6 +212,7 @@
icon_state = "cyborg_upgrade3"
require_module = 1
module_type = list(/obj/item/robot_module/butler)
+ module_flags = BORG_MODULE_JANITOR
/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R)
. = ..()
@@ -234,6 +240,7 @@
icon_state = "cyborg_upgrade3"
require_module = 1
module_type = list(/obj/item/robot_module/butler)
+ module_flags = BORG_MODULE_JANITOR
/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R)
. = ..()
@@ -241,9 +248,9 @@
for(var/obj/item/mop/cyborg/M in R.module.modules)
R.module.remove_module(M, TRUE)
- var/obj/item/mop/advanced/cyborg/A = new /obj/item/mop/advanced/cyborg(R.module)
- R.module.basic_modules += A
- R.module.add_module(A, FALSE, TRUE)
+ var/obj/item/mop/advanced/cyborg/A = new /obj/item/mop/advanced/cyborg(R.module)
+ R.module.basic_modules += A
+ R.module.add_module(A, FALSE, TRUE)
/obj/item/borg/upgrade/amop/deactivate(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -283,6 +290,7 @@
resistance_flags = LAVA_PROOF | FIRE_PROOF
require_module = 1
module_type = list(/obj/item/robot_module/miner)
+ module_flags = BORG_MODULE_MINER
/obj/item/borg/upgrade/lavaproof/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -402,6 +410,7 @@
module_type = list(/obj/item/robot_module/medical,
/obj/item/robot_module/syndicate_medical)
var/list/additional_reagents = list()
+ module_flags = BORG_MODULE_MEDICAL
/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -471,6 +480,7 @@
require_module = 1
module_type = list(/obj/item/robot_module/medical,
/obj/item/robot_module/syndicate_medical)
+ module_flags = BORG_MODULE_MEDICAL
/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -566,7 +576,7 @@
/obj/item/borg/upgrade/expand/deactivate(mob/living/silicon/robot/R, user = usr)
. = ..()
- if (.)
+ if (. && R.hasExpanded)
R.resize = 0.5
R.hasExpanded = FALSE
R.update_transform()
@@ -578,6 +588,7 @@
icon_state = "borg_BS_RPED"
require_module = TRUE
module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur)
+ module_flags = BORG_MODULE_ENGINEERING
/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -612,8 +623,8 @@
icon = 'icons/obj/device.dmi'
icon_state = "pinpointer_crew"
require_module = TRUE
- module_type = list(/obj/item/robot_module/medical,
- /obj/item/robot_module/syndicate_medical)
+ module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical)
+ module_flags = BORG_MODULE_MEDICAL
/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr)
. = ..()
diff --git a/code/game/objects/items/sharpener.dm b/code/game/objects/items/sharpener.dm
index 6bf0b27fb4..fc19e61cd6 100644
--- a/code/game/objects/items/sharpener.dm
+++ b/code/game/objects/items/sharpener.dm
@@ -39,7 +39,7 @@
I.force = clamp(I.force + increment, 0, max)
user.visible_message("[user] sharpens [I] with [src]!", "You sharpen [I], making it much more deadly than before.")
- I.sharpness = IS_SHARP_ACCURATE
+ I.sharpness = SHARP_POINTY
I.throwforce = clamp(I.throwforce + increment, 0, max)
I.name = "[prefix] [I.name]"
name = "worn out [name]"
diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm
index 154714b13f..4952c93928 100644
--- a/code/game/objects/items/shields.dm
+++ b/code/game/objects/items/shields.dm
@@ -106,8 +106,8 @@
var/disarming = (target_downed && (shield_flags & SHIELD_BASH_GROUND_SLAM_DISARM)) || (shield_flags & SHIELD_BASH_ALWAYS_DISARM) || (wallhit && (shield_flags & SHIELD_BASH_WALL_DISARM))
var/knockdown = !target_downed && ((shield_flags & SHIELD_BASH_ALWAYS_KNOCKDOWN) || (wallhit && (shield_flags & SHIELD_BASH_WALL_KNOCKDOWN)))
if(shieldbash_stagger_duration || knockdown)
- target.visible_message("[target] is knocked [knockdown? "to the floor" : "off balanace"]!",
- "You are knocked [knockdown? "to the floor" : "off balanace"]!")
+ target.visible_message("[target] is knocked [knockdown? "to the floor" : "off balance"]!",
+ "You are knocked [knockdown? "to the floor" : "off balance"]!")
if(knockdown)
target.KnockToFloor(disarming)
else if(disarming)
@@ -446,6 +446,12 @@ obj/item/shield/riot/bullet_proof
return BLOCK_SUCCESS | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT
return ..()
+/obj/item/shield/energy/active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, override_direction)
+ if((attack_type & ATTACK_TYPE_PROJECTILE) && is_energy_reflectable_projectile(object))
+ block_return[BLOCK_RETURN_REDIRECT_METHOD] = REDIRECT_METHOD_DEFLECT
+ return BLOCK_SUCCESS | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT
+ return ..()
+
/obj/item/shield/energy/attack_self(mob/living/carbon/human/user)
if(clumsy_check && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
to_chat(user, "You beat yourself in the head with [src]!")
diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm
index 2f40604719..ced0ee3160 100644
--- a/code/game/objects/items/shooting_range.dm
+++ b/code/game/objects/items/shooting_range.dm
@@ -31,10 +31,7 @@
to_chat(user, "You slice off [src]'s uneven chunks of aluminium and scorch marks.")
return TRUE
-/obj/item/target/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/item/target/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(pinnedLoc)
pinnedLoc.removeTarget(user)
diff --git a/code/game/objects/items/shrapnel.dm b/code/game/objects/items/shrapnel.dm
index 7108080ecc..5904cb6c0b 100644
--- a/code/game/objects/items/shrapnel.dm
+++ b/code/game/objects/items/shrapnel.dm
@@ -7,11 +7,13 @@
icon_state = "large"
w_class = WEIGHT_CLASS_TINY
item_flags = DROPDEL
+ sharpness = SHARP_EDGED
/obj/item/shrapnel/stingball // stingbang grenades
name = "stingball"
embedding = list(embed_chance=90, fall_chance=3, jostle_chance=7, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.7, pain_mult=5, jostle_pain_mult=6, rip_time=15, embed_chance_turf_mod=-100)
icon_state = "tiny"
+ sharpness = SHARP_NONE
/obj/item/shrapnel/bullet // bullets
name = "bullet"
@@ -28,21 +30,24 @@
/obj/item/projectile/bullet/shrapnel
name = "flying shrapnel shard"
- damage = 9
- range = 10
+ damage = 14
+ range = 20
armour_penetration = -30
dismemberment = 5
ricochets_max = 2
- ricochet_chance = 40
+ ricochet_chance = 70
shrapnel_type = /obj/item/shrapnel
ricochet_incidence_leeway = 60
+ sharpness = SHARP_EDGED
+ wound_bonus = 40
/obj/item/projectile/bullet/shrapnel/mega
name = "flying shrapnel hunk"
- range = 25
- dismemberment = 10
- ricochets_max = 4
- ricochet_chance = 90
+ range = 45
+ dismemberment = 15
+ ricochets_max = 6
+ ricochet_chance = 130
+ ricochet_incidence_leeway = 0
ricochet_decay_chance = 0.9
/obj/item/projectile/bullet/pellet/stingball
@@ -62,3 +67,15 @@
name = "megastingball pellet"
ricochets_max = 6
ricochet_chance = 110
+
+/obj/item/projectile/bullet/pellet/stingball/breaker
+ name = "breakbang pellet"
+ damage = 10
+ wound_bonus = 40
+ sharpness = SHARP_NONE
+
+/obj/item/projectile/bullet/pellet/stingball/shred
+ name = "shredbang pellet"
+ damage = 10
+ wound_bonus = 30
+ sharpness = SHARP_EDGED
diff --git a/code/game/objects/items/signs.dm b/code/game/objects/items/signs.dm
index cf7373b700..67bc28b2ea 100644
--- a/code/game/objects/items/signs.dm
+++ b/code/game/objects/items/signs.dm
@@ -40,4 +40,3 @@
user.visible_message("[user] waves around \the \"[label]\" sign.")
else
user.visible_message("[user] waves around blank sign.")
- user.changeNext_move(CLICK_CD_MELEE)
\ No newline at end of file
diff --git a/code/game/objects/items/spear.dm b/code/game/objects/items/spear.dm
index 376362d7c3..f40c774551 100644
--- a/code/game/objects/items/spear.dm
+++ b/code/game/objects/items/spear.dm
@@ -15,13 +15,15 @@
custom_materials = list(/datum/material/iron=1150, /datum/material/glass=2075)
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "poked", "jabbed", "torn", "gored")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
var/obj/item/grenade/explosive = null
var/war_cry = "AAAAARGH!!!"
var/icon_prefix = "spearglass"
var/wielded = FALSE // track wielded status on item
+ wound_bonus = -15
+ bare_wound_bonus = 15
/obj/item/spear/Initialize()
. = ..()
@@ -177,7 +179,7 @@
custom_materials = null
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "poked", "jabbed", "torn", "gored")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
icon_prefix = "bone_spear"
/obj/item/spear/bonespear/ComponentInitialize()
diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm
index 00e48fd12a..3b4be37cee 100644
--- a/code/game/objects/items/stacks/bscrystal.dm
+++ b/code/game/objects/items/stacks/bscrystal.dm
@@ -74,8 +74,7 @@
/obj/item/stack/sheet/bluespace_crystal/attack_self(mob/user)// to prevent the construction menu from ever happening
to_chat(user, "You cannot crush the polycrystal in-hand, try breaking one off.")
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/stack/sheet/bluespace_crystal/attack_hand(mob/user)
+/obj/item/stack/sheet/bluespace_crystal/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(user.get_inactive_held_item() == src)
if(zero_amount())
return
diff --git a/code/game/objects/items/stacks/cash.dm b/code/game/objects/items/stacks/cash.dm
index ce0bc6591a..954950f5e6 100644
--- a/code/game/objects/items/stacks/cash.dm
+++ b/code/game/objects/items/stacks/cash.dm
@@ -11,6 +11,7 @@
w_class = WEIGHT_CLASS_TINY
full_w_class = WEIGHT_CLASS_TINY
resistance_flags = FLAMMABLE
+ grind_results = list(/datum/reagent/cellulose = 10)
var/value = 0
/obj/item/stack/spacecash/Initialize()
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index e6403e83cc..9ea8c9c689 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/medical.dm
@@ -15,11 +15,20 @@
var/self_delay = 50
var/other_delay = 0
var/repeating = FALSE
+ /// How much brute we heal per application
+ var/heal_brute
+ /// How much burn we heal per application
+ var/heal_burn
+ /// How much we reduce bleeding per application on cut wounds
+ var/stop_bleeding
+ /// How much sanitization to apply to burns on application
+ var/sanitization
+ /// How much we add to flesh_healing for burn wounds on application
+ var/flesh_regeneration
/obj/item/stack/medical/attack(mob/living/M, mob/user)
. = ..()
- try_heal(M, user)
-
+ INVOKE_ASYNC(src, .proc/try_heal, M, user)
/obj/item/stack/medical/proc/try_heal(mob/living/M, mob/user, silent = FALSE)
if(!M.can_inject(user, TRUE))
@@ -70,8 +79,9 @@
icon_state = "brutepack"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- var/heal_brute = 20
- self_delay = 20
+ heal_brute = 40
+ self_delay = 40
+ other_delay = 20
grind_results = list(/datum/reagent/medicine/styptic_powder = 10)
/obj/item/stack/medical/bruise_pack/one
@@ -95,7 +105,8 @@
M.heal_bodypart_damage((heal_brute/2))
return TRUE
if(iscarbon(M))
- return heal_carbon(M, user, heal_brute, 0)
+ return heal_carbon(M, user, heal_brute, heal_burn)
+ to_chat(user, "You can't heal [M] with \the [src]!")
to_chat(user, "You can't heal [M] with the \the [src]!")
/obj/item/stack/medical/bruise_pack/suicide_act(mob/user)
@@ -104,24 +115,51 @@
/obj/item/stack/medical/gauze
name = "medical gauze"
- desc = "A roll of elastic cloth that is extremely effective at stopping bleeding, heals minor wounds."
+ desc = "A roll of elastic cloth, perfect for stabilizing all kinds of wounds, from cuts and burns to broken bones."
gender = PLURAL
singular_name = "medical gauze"
icon_state = "gauze"
- var/stop_bleeding = 1800
- var/heal_brute = 5
- self_delay = 10
+ heal_brute = 5
+ self_delay = 50
+ other_delay = 20
+ amount = 10
+ max_amount = 10
+ absorption_rate = 0.25
+ absorption_capacity = 5
+ splint_factor = 0.35
custom_price = PRICE_REALLY_CHEAP
-/obj/item/stack/medical/gauze/heal(mob/living/M, mob/user)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(!H.bleedsuppress && H.bleed_rate) //so you can't stack bleed suppression
- H.suppress_bloodloss(stop_bleeding)
- to_chat(user, "You stop the bleeding of [M]!")
- H.adjustBruteLoss(-(heal_brute))
- return TRUE
- to_chat(user, "You can not use \the [src] on [M]!")
+// gauze is only relevant for wounds, which are handled in the wounds themselves
+/obj/item/stack/medical/gauze/try_heal(mob/living/M, mob/user, silent)
+ var/obj/item/bodypart/limb = M.get_bodypart(check_zone(user.zone_selected))
+ if(!limb)
+ to_chat(user, "There's nothing there to bandage!")
+ return
+ if(!LAZYLEN(limb.wounds))
+ to_chat(user, "There's no wounds that require bandaging on [user==M ? "your" : "[M]'s"] [limb.name]!") // good problem to have imo
+ return
+
+ var/gauzeable_wound = FALSE
+ for(var/i in limb.wounds)
+ var/datum/wound/woundies = i
+ if(woundies.wound_flags & ACCEPTS_GAUZE)
+ gauzeable_wound = TRUE
+ break
+ if(!gauzeable_wound)
+ to_chat(user, "There's no wounds that require bandaging on [user==M ? "your" : "[M]'s"] [limb.name]!") // good problem to have imo
+ return
+
+ if(limb.current_gauze && (limb.current_gauze.absorption_capacity * 0.8 > absorption_capacity)) // ignore if our new wrap is < 20% better than the current one, so someone doesn't bandage it 5 times in a row
+ to_chat(user, "The bandage currently on [user==M ? "your" : "[M]'s"] [limb.name] is still in good condition!")
+ return
+
+ user.visible_message("[user] begins wrapping the wounds on [M]'s [limb.name] with [src]...", "You begin wrapping the wounds on [user == M ? "your" : "[M]'s"] [limb.name] with [src]...")
+
+ if(!do_after(user, (user == M ? self_delay : other_delay), target=M))
+ return
+
+ user.visible_message("[user] applies [src] to [M]'s [limb.name].", "You bandage the wounds on [user == M ? "yourself" : "[M]'s"] [limb.name].")
+ limb.apply_gauze(src)
/obj/item/stack/medical/gauze/attackby(obj/item/I, mob/user, params)
if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness())
@@ -133,6 +171,14 @@
"You cut [src] into pieces of cloth with [I].", \
"You hear cutting.")
use(2)
+ else if(I.is_drainable() && I.reagents.has_reagent(/datum/reagent/space_cleaner/sterilizine))
+ if(!I.reagents.has_reagent(/datum/reagent/space_cleaner/sterilizine, 10))
+ to_chat(user, "There's not enough sterilizine in [I] to sterilize [src]!")
+ return
+ user.visible_message("[user] pours the contents of [I] onto [src], sterilizing it.", "You pour the contents of [I] onto [src], sterilizing it.")
+ I.reagents.remove_reagent(/datum/reagent/space_cleaner/sterilizine, 10)
+ new /obj/item/stack/medical/gauze/adv/one(user.drop_location())
+ use(1)
else
return ..()
@@ -143,15 +189,22 @@
/obj/item/stack/medical/gauze/improvised
name = "improvised gauze"
singular_name = "improvised gauze"
- desc = "A roll of cloth roughly cut from something that can stop bleeding, but does not heal wounds."
- stop_bleeding = 900
heal_brute = 0
+ desc = "A roll of cloth roughly cut from something that does a decent job of stabilizing wounds, but less efficiently than real medical gauze."
+ self_delay = 60
+ other_delay = 30
+ absorption_rate = 0.15
+ absorption_capacity = 4
/obj/item/stack/medical/gauze/adv
name = "sterilized medical gauze"
- desc = "A roll of elastic sterilized cloth that is extremely effective at stopping bleeding, heals minor wounds and cleans them."
singular_name = "sterilized medical gauze"
- self_delay = 5
+ desc = "A roll of elastic sterilized cloth that is extremely effective at stopping bleeding and covering burns."
+ heal_brute = 6
+ self_delay = 45
+ other_delay = 15
+ absorption_rate = 0.4
+ absorption_capacity = 6
/obj/item/stack/medical/gauze/adv/one
amount = 1
@@ -161,38 +214,9 @@
is_cyborg = 1
cost = 250
-/obj/item/stack/medical/ointment
- name = "ointment"
- desc = "Used to treat those nasty burn wounds."
- gender = PLURAL
- singular_name = "ointment"
- icon_state = "ointment"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- var/heal_burn = 20
- self_delay = 20
- grind_results = list(/datum/reagent/medicine/silver_sulfadiazine = 10)
-
-/obj/item/stack/medical/ointment/one
- amount = 1
-
-/obj/item/stack/medical/ointment/heal(mob/living/M, mob/user)
- if(M.stat == DEAD)
- to_chat(user, " [M] is dead. You can not help [M.p_them()]!")
- return
- if(iscarbon(M))
- return heal_carbon(M, user, 0, heal_burn)
- if(AmBloodsucker(M))
- return
- to_chat(user, "You can't heal [M] with the \the [src]!")
-
-/obj/item/stack/medical/ointment/suicide_act(mob/living/user)
- user.visible_message("[user] is squeezing \the [src] into [user.p_their()] mouth! [user.p_do(TRUE)]n't [user.p_they()] know that stuff is toxic?")
- return TOXLOSS
-
/obj/item/stack/medical/suture
name = "suture"
- desc = "Sterile sutures used to seal up cuts and lacerations."
+ desc = "Basic sterile sutures used to seal up cuts and lacerations and stop bleeding."
gender = PLURAL
singular_name = "suture"
icon_state = "suture"
@@ -201,9 +225,30 @@
amount = 15
max_amount = 15
repeating = TRUE
- var/heal_brute = 10
+ heal_brute = 10
+ stop_bleeding = 0.6
grind_results = list(/datum/reagent/medicine/spaceacillin = 2)
+/obj/item/stack/medical/suture/emergency
+ name = "emergency suture"
+ desc = "A value pack of cheap sutures, not very good at repairing damage, but still decent at stopping bleeding."
+ heal_brute = 5
+ amount = 5
+ max_amount = 5
+
+/obj/item/stack/medical/suture/one
+ amount = 1
+
+/obj/item/stack/medical/suture/five
+ amount = 5
+
+/obj/item/stack/medical/suture/medicated
+ name = "medicated suture"
+ icon_state = "suture_purp"
+ desc = "A suture infused with drugs that speed up wound healing of the treated laceration."
+ heal_brute = 15
+ grind_results = list(/datum/reagent/medicine/polypyr = 2)
+
/obj/item/stack/medical/suture/one
amount = 1
@@ -223,10 +268,39 @@
to_chat(user, "[M] is at full health.")
return FALSE
user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].")
- M.heal_bodypart_damage(heal_brute)
- return TRUE
+ return heal_carbon(M, user, heal_brute, heal_burn)
- to_chat(user, "You can't heal [M] with the \the [src]!")
+ to_chat(user, "You can't heal [M] with \the [src]!")
+
+/obj/item/stack/medical/ointment
+ name = "ointment"
+ desc = "Basic burn ointment, rated effective for second degree burns with proper bandaging, though it's still an effective stabilizer for worse burns. Not terribly good at outright healing burns though."
+ gender = PLURAL
+ singular_name = "ointment"
+ icon_state = "ointment"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ amount = 12
+ max_amount = 12
+ self_delay = 40
+ other_delay = 20
+
+ heal_burn = 5
+ flesh_regeneration = 2.5
+ sanitization = 0.3
+ grind_results = list(/datum/reagent/medicine/kelotane = 10)
+
+/obj/item/stack/medical/ointment/heal(mob/living/M, mob/user)
+ if(M.stat == DEAD)
+ to_chat(user, "[M] is dead! You can not help [M.p_them()].")
+ return
+ if(iscarbon(M))
+ return heal_carbon(M, user, heal_brute, heal_burn)
+ to_chat(user, "You can't heal [M] with \the [src]!")
+
+/obj/item/stack/medical/ointment/suicide_act(mob/living/user)
+ user.visible_message("[user] is squeezing \the [src] into [user.p_their()] mouth! [user.p_do(TRUE)]n't [user.p_they()] know that stuff is toxic?")
+ return TOXLOSS
/obj/item/stack/medical/mesh
name = "regenerative mesh"
@@ -238,20 +312,43 @@
other_delay = 10
amount = 15
max_amount = 15
+ heal_burn = 10
repeating = TRUE
- var/heal_burn = 10
+ sanitization = 0.75
+ flesh_regeneration = 3
var/is_open = TRUE ///This var determines if the sterile packaging of the mesh has been opened.
grind_results = list(/datum/reagent/medicine/spaceacillin = 2)
/obj/item/stack/medical/mesh/one
amount = 1
+/obj/item/stack/medical/mesh/five
+ amount = 5
+
+/obj/item/stack/medical/mesh/advanced
+ name = "advanced regenerative mesh"
+ desc = "An advanced mesh made with aloe extracts and sterilizing chemicals, used to treat burns."
+ gender = PLURAL
+ singular_name = "advanced regenerative mesh"
+ icon_state = "aloe_mesh"
+ heal_burn = 15
+ grind_results = list(/datum/reagent/consumable/aloejuice = 1)
+
+/obj/item/stack/medical/mesh/advanced/one
+ amount = 1
+
/obj/item/stack/medical/mesh/Initialize()
. = ..()
if(amount == max_amount) //only seal full mesh packs
is_open = FALSE
update_icon()
+/obj/item/stack/medical/mesh/advanced/update_icon_state()
+ if(!is_open)
+ icon_state = "aloe_mesh_closed"
+ else
+ return ..()
+
/obj/item/stack/medical/mesh/update_icon_state()
if(!is_open)
icon_state = "regen_mesh_closed"
@@ -264,8 +361,8 @@
to_chat(user, "[M] is dead! You can not help [M.p_them()].")
return
if(iscarbon(M))
- return heal_carbon(M, user, 0, heal_burn)
- to_chat(user, "You can't heal [M] with the \the [src]!")
+ return heal_carbon(M, user, heal_brute, heal_burn)
+ to_chat(user, "You can't heal [M] with \the [src]!")
/obj/item/stack/medical/mesh/try_heal(mob/living/M, mob/user, silent = FALSE)
@@ -280,7 +377,7 @@
return
. = ..()
-/obj/item/stack/medical/mesh/attack_hand(mob/user)
+/obj/item/stack/medical/mesh/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!is_open & user.get_inactive_held_item() == src)
to_chat(user, "You need to open [src] first.")
return
@@ -294,3 +391,83 @@
playsound(src, 'sound/items/poster_ripped.ogg', 20, TRUE)
return
. = ..()
+
+/obj/item/stack/medical/bone_gel
+ name = "bone gel"
+ singular_name = "bone gel"
+ desc = "A potent medical gel that, when applied to a damaged bone in a proper surgical setting, triggers an intense melding reaction to repair the wound. Can be directly applied alongside surgical sticky tape to a broken bone in dire circumstances, though this is very harmful to the patient and not recommended."
+
+ icon = 'icons/obj/surgery.dmi'
+ icon_state = "bone-gel"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+
+ amount = 4
+ self_delay = 20
+ grind_results = list(/datum/reagent/medicine/bicaridine = 10)
+ novariants = TRUE
+
+/obj/item/stack/medical/bone_gel/attack(mob/living/M, mob/user)
+ to_chat(user, "Bone gel can only be used on fractured limbs while aggressively holding someone!")
+ return
+
+/obj/item/stack/medical/bone_gel/suicide_act(mob/user)
+ if(iscarbon(user))
+ var/mob/living/carbon/C = user
+ C.visible_message("[C] is squirting all of \the [src] into [C.p_their()] mouth! That's not proper procedure! It looks like [C.p_theyre()] trying to commit suicide!")
+ if(do_after(C, 2 SECONDS))
+ C.emote("scream")
+ for(var/i in C.bodyparts)
+ var/obj/item/bodypart/bone = i
+ var/datum/wound/blunt/severe/oof_ouch = new
+ oof_ouch.apply_wound(bone)
+ var/datum/wound/blunt/critical/oof_OUCH = new
+ oof_OUCH.apply_wound(bone)
+
+ for(var/i in C.bodyparts)
+ var/obj/item/bodypart/bone = i
+ bone.receive_damage(brute=60)
+ use(1)
+ return (BRUTELOSS)
+ else
+ C.visible_message("[C] screws up like an idiot and still dies anyway!")
+ return (BRUTELOSS)
+
+/obj/item/stack/medical/bone_gel/cyborg
+ custom_materials = null
+ is_cyborg = 1
+ cost = 250
+
+/obj/item/stack/medical/aloe
+ name = "aloe cream"
+ desc = "A healing paste you can apply on wounds."
+
+ icon_state = "aloe_paste"
+ self_delay = 20
+ other_delay = 10
+ novariants = TRUE
+ amount = 20
+ max_amount = 20
+ var/heal = 3
+ grind_results = list(/datum/reagent/consumable/aloejuice = 1)
+
+/obj/item/stack/medical/aloe/heal(mob/living/M, mob/user)
+ . = ..()
+ if(M.stat == DEAD)
+ to_chat(user, "[M] is dead! You can not help [M.p_them()].")
+ return FALSE
+ if(iscarbon(M))
+ return heal_carbon(M, user, heal, heal)
+ if(isanimal(M))
+ var/mob/living/simple_animal/critter = M
+ if (!(critter.healable))
+ to_chat(user, "You cannot use \the [src] on [M]!")
+ return FALSE
+ else if (critter.health == critter.maxHealth)
+ to_chat(user, "[M] is at full health.")
+ return FALSE
+ user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].")
+ M.heal_bodypart_damage(heal, heal)
+ return TRUE
+
+ to_chat(user, "You can't heal [M] with the \the [src]!")
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index d4baea2487..29b4dea6d8 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -289,7 +289,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
resistance_flags = ACID_PROOF
armor = list("melee" = 100, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100)
max_integrity = 40
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
var/icon_prefix
embedding = list("embed_chance" = 65)
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 3ca7137548..7bc66e8deb 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -121,6 +121,8 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
new/datum/stack_recipe("iron door", /obj/structure/mineral_door/iron, 20, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("pestle", /obj/item/pestle, 1, time = 50), \
new/datum/stack_recipe("floodlight frame", /obj/structure/floodlight_frame, 5, one_per_turf = TRUE, on_floor = TRUE), \
+ null, \
+ new/datum/stack_recipe("tiny fan", /obj/structure/fans/tiny, 2, time = 4, one_per_turf = TRUE, on_floor = TRUE), \
))
/obj/item/stack/sheet/metal
@@ -184,13 +186,13 @@ GLOBAL_LIST_INIT(plasteel_recipes, list ( \
new /datum/stack_recipe("trash cart", /obj/structure/closet/crate/trashcart, 5, time = 50, one_per_turf = 1, on_floor = 1), \
new /datum/stack_recipe("medical crate", /obj/structure/closet/crate/medical, 5, time = 50, one_per_turf = 1, on_floor = 1), \
new /datum/stack_recipe("freezer crate", /obj/structure/closet/crate/freezer, 8, time = 50, one_per_turf = 1, on_floor = 1), \
- new /datum/stack_recipe("blood bag crate", /obj/structure/closet/crate/freezer/blood, 8, time = 50, one_per_turf = 1, on_floor = 1), \
- new /datum/stack_recipe("surplus limbs crate", /obj/structure/closet/crate/freezer/surplus_limbs, 8, time = 50, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("blood bag crate", /obj/structure/closet/crate/freezer/blood/fake, 8, time = 50, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("surplus limbs crate", /obj/structure/closet/crate/freezer/surplus_limbs/fake, 8, time = 50, one_per_turf = 1, on_floor = 1), \
new /datum/stack_recipe("radiation containment crate", /obj/structure/closet/crate/radiation, 8, time = 50, one_per_turf = 1, on_floor = 1), \
new /datum/stack_recipe("hydroponics crate", /obj/structure/closet/crate/hydroponics, 5, time = 50, one_per_turf = 1, on_floor = 1), \
new /datum/stack_recipe("engineering crate", /obj/structure/closet/crate/engineering, 5, time = 50, one_per_turf = 1, on_floor = 1), \
new /datum/stack_recipe("eletrical crate", /obj/structure/closet/crate/engineering/electrical, 5, time = 50, one_per_turf = 1, on_floor = 1), \
- new /datum/stack_recipe("RCD storage crate", /obj/structure/closet/crate/rcd, 5, time = 50, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("RCD storage crate", /obj/structure/closet/crate/rcd/fake, 5, time = 50, one_per_turf = 1, on_floor = 1), \
new /datum/stack_recipe("science crate", /obj/structure/closet/crate/science, 5, time = 50, one_per_turf = 1, on_floor = 1), \
)), \
new /datum/stack_recipe_list("airlock assemblies", list( \
@@ -244,8 +246,10 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \
new/datum/stack_recipe("rifle stock", /obj/item/weaponcrafting/stock, 10, time = 20), \
new/datum/stack_recipe("rolling pin", /obj/item/kitchen/rollingpin, 2, time = 30), \
new/datum/stack_recipe("wooden bucket", /obj/item/reagent_containers/glass/bucket/wood, 2, time = 30), \
+ new/datum/stack_recipe("painting frame", /obj/item/wallframe/painting, 1, time = 10),\
new/datum/stack_recipe("wooden buckler", /obj/item/shield/riot/buckler, 20, time = 40), \
new/datum/stack_recipe("baseball bat", /obj/item/melee/baseball_bat, 5, time = 15),\
+ new/datum/stack_recipe("training bokken", /obj/item/melee/bokken, 10, time = 15),\
null, \
new/datum/stack_recipe("wooden chair", /obj/structure/chair/wood/, 3, time = 10, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("winged wooden chair", /obj/structure/chair/wood/wings, 3, time = 10, one_per_turf = TRUE, on_floor = TRUE), \
@@ -287,7 +291,7 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \
merge_type = /obj/item/stack/sheet/mineral/wood
novariants = TRUE
material_type = /datum/material/wood
- grind_results = list(/datum/reagent/carbon = 20)
+ grind_results = list(/datum/reagent/cellulose = 20)
walltype = /turf/closed/wall/mineral/wood
/obj/item/stack/sheet/mineral/wood/attackby(obj/item/W, mob/user, params) // NOTE: sheet_types.dm is where the WOOD stack lives. Maybe move this over there.
@@ -349,7 +353,7 @@ GLOBAL_LIST_INIT(bamboo_recipes, list ( \
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0)
resistance_flags = FLAMMABLE
merge_type = /obj/item/stack/sheet/mineral/bamboo
- grind_results = list(/datum/reagent/carbon = 5)
+ grind_results = list(/datum/reagent/cellulose = 10)
material_type = /datum/material/bamboo
/obj/item/stack/sheet/mineral/bamboo/get_main_recipes()
@@ -380,6 +384,7 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \
new/datum/stack_recipe("mining satchel", /obj/item/storage/bag/ore, 4), \
new/datum/stack_recipe("chemistry bag", /obj/item/storage/bag/chemistry, 4), \
new/datum/stack_recipe("bio bag", /obj/item/storage/bag/bio, 4), \
+ new/datum/stack_recipe("construction bag", /obj/item/storage/bag/construction, 4), \
null, \
new/datum/stack_recipe("string", /obj/item/weaponcrafting/string, 1, time = 10), \
new/datum/stack_recipe("improvised gauze", /obj/item/stack/medical/gauze/improvised, 1, 2, 6), \
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index 16b46567c7..3e2bb675fa 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -25,9 +25,18 @@
var/list/mats_per_unit //list that tells you how much is in a single unit.
///Datum material type that this stack is made of
var/material_type
+ max_integrity = 100
//NOTE: When adding grind_results, the amounts should be for an INDIVIDUAL ITEM - these amounts will be multiplied by the stack size in on_grind()
var/obj/structure/table/tableVariant // we tables now (stores table variant to be built from this stack)
+ // The following are all for medical treatment, they're here instead of /stack/medical because sticky tape can be used as a makeshift bandage or splint
+ /// If set and this used as a splint for a broken bone wound, this is used as a multiplier for applicable slowdowns (lower = better) (also for speeding up burn recoveries)
+ var/splint_factor
+ /// How much blood flow this stack can absorb if used as a bandage on a cut wound, note that absorption is how much we lower the flow rate, not the raw amount of blood we suck up
+ var/absorption_capacity
+ /// How quickly we lower the blood flow on a cut wound we're bandaging. Expected lifetime of this bandage in ticks is thus absorption_capacity/absorption_rate, or until the cut heals, whichever comes first
+ var/absorption_rate
+
/obj/item/stack/on_grind()
for(var/i in 1 to grind_results.len) //This should only call if it's ground, so no need to check if grind_results exists
grind_results[grind_results[i]] *= get_amount() //Gets the key at position i, then the reagent amount of that key, then multiplies it by stack size
@@ -224,7 +233,7 @@
return
T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR)
else
- O = new R.result_type(usr.drop_location())
+ O = new R.result_type(get_turf(usr))
if(O)
O.setDir(usr.dir)
log_craft("[O] crafted by [usr] at [loc_name(O.loc)]")
@@ -389,8 +398,7 @@
merge(AM)
. = ..()
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/stack/attack_hand(mob/user)
+/obj/item/stack/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(user.get_inactive_held_item() == src)
if(zero_amount())
return
diff --git a/code/game/objects/items/stacks/tape.dm b/code/game/objects/items/stacks/tape.dm
index 177260febb..bbfee8dece 100644
--- a/code/game/objects/items/stacks/tape.dm
+++ b/code/game/objects/items/stacks/tape.dm
@@ -7,14 +7,21 @@
icon = 'icons/obj/tapes.dmi'
icon_state = "tape_w"
var/prefix = "sticky"
+ w_class = WEIGHT_CLASS_TINY
+ full_w_class = WEIGHT_CLASS_TINY
item_flags = NOBLUDGEON
amount = 5
max_amount = 5
resistance_flags = FLAMMABLE
+ splint_factor = 0.8
+ grind_results = list(/datum/reagent/cellulose = 5)
var/list/conferred_embed = EMBED_HARMLESS
var/overwrite_existing = FALSE
+ var/endless = FALSE
+ var/apply_time = 30
+
/obj/item/stack/sticky_tape/afterattack(obj/item/I, mob/living/user)
if(!istype(I))
return
@@ -25,17 +32,24 @@
user.visible_message("[user] begins wrapping [I] with [src].", "You begin wrapping [I] with [src].")
- if(do_after(user, 30, target=I))
+ if(do_after(user, apply_time, target=I))
I.embedding = conferred_embed
I.updateEmbedding()
to_chat(user, "You finish wrapping [I] with [src].")
- use(1)
+ if(!endless)
+ use(1)
I.name = "[prefix] [I.name]"
if(istype(I, /obj/item/grenade))
var/obj/item/grenade/sticky_bomb = I
sticky_bomb.sticky = TRUE
+/obj/item/stack/sticky_tape/infinite //endless tape that applies far faster, for maximum honks
+ name = "endless sticky tape"
+ desc = "This roll of sticky tape somehow has no end."
+ endless = TRUE
+ apply_time = 10
+
/obj/item/stack/sticky_tape/super
name = "super sticky tape"
singular_name = "super sticky tape"
@@ -43,6 +57,7 @@
icon_state = "tape_y"
prefix = "super sticky"
conferred_embed = EMBED_HARMLESS_SUPERIOR
+ splint_factor = 0.6
/obj/item/stack/sticky_tape/pointy
name = "pointy tape"
@@ -58,4 +73,14 @@
desc = "You didn't know tape could look so sinister. Welcome to Space Station 13."
icon_state = "tape_spikes"
prefix = "super pointy"
- conferred_embed = EMBED_POINTY_SUPERIOR
\ No newline at end of file
+ conferred_embed = EMBED_POINTY_SUPERIOR
+
+/obj/item/stack/sticky_tape/surgical
+ name = "surgical tape"
+ singular_name = "surgical tape"
+ desc = "Made for patching broken bones back together alongside bone gel, not for playing pranks."
+ //icon_state = "tape_spikes"
+ prefix = "surgical"
+ conferred_embed = list("embed_chance" = 30, "pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE)
+ splint_factor = 0.4
+ custom_price = 500
diff --git a/code/game/objects/items/stacks/tickets.dm b/code/game/objects/items/stacks/tickets.dm
new file mode 100644
index 0000000000..22cb895277
--- /dev/null
+++ b/code/game/objects/items/stacks/tickets.dm
@@ -0,0 +1,31 @@
+/obj/item/stack/arcadeticket
+ name = "arcade tickets"
+ desc = "Wow! With enough of these, you could buy a bike! ...Pssh, yeah right."
+ singular_name = "arcade ticket"
+ icon_state = "arcade-ticket"
+ item_state = "tickets"
+ w_class = WEIGHT_CLASS_TINY
+ max_amount = 30
+
+/obj/item/stack/arcadeticket/Initialize(mapload, new_amount, merge = TRUE)
+ . = ..()
+ update_icon()
+
+/obj/item/stack/arcadeticket/update_icon()
+ var/amount = get_amount()
+ if((amount >= 12) && (amount > 0))
+ icon_state = "arcade-ticket_4"
+ else if((amount >= 6) && (amount > 0))
+ icon_state = "arcade-ticket_3"
+ else if((amount >= 2) && (amount > 0))
+ icon_state = "arcade-ticket_2"
+ else
+ icon_state = "arcade-ticket"
+
+/obj/item/stack/arcadeticket/proc/pay_tickets()
+ amount -= 2
+ if (amount == 0)
+ qdel(src)
+
+/obj/item/stack/arcadeticket/thirty
+ amount = 30
diff --git a/code/game/objects/items/stacks/wrap.dm b/code/game/objects/items/stacks/wrap.dm
index 10240e902b..6ae63e640f 100644
--- a/code/game/objects/items/stacks/wrap.dm
+++ b/code/game/objects/items/stacks/wrap.dm
@@ -35,6 +35,7 @@
amount = 25
max_amount = 25
resistance_flags = FLAMMABLE
+ grind_results = list(/datum/reagent/cellulose = 5)
/obj/item/stack/packageWrap/suicide_act(mob/living/user)
user.visible_message("[user] begins wrapping [user.p_them()]self in \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
diff --git a/code/game/objects/items/storage/_storage.dm b/code/game/objects/items/storage/_storage.dm
index fd4cc793ab..2fc1b6484d 100644
--- a/code/game/objects/items/storage/_storage.dm
+++ b/code/game/objects/items/storage/_storage.dm
@@ -19,8 +19,9 @@
return TRUE
/obj/item/storage/contents_explosion(severity, target)
+ var/in_storage = istype(loc, /obj/item/storage)? (max(0, severity - 1)) : (severity)
for(var/atom/A in contents)
- A.ex_act(severity, target)
+ A.ex_act(in_storage, target)
CHECK_TICK
//Cyberboss says: "USE THIS TO FILL IT, NOT INITIALIZE OR NEW"
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index 3d5f0dc924..f15588a7f1 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -369,6 +369,7 @@
new /obj/item/circular_saw(src)
new /obj/item/surgicaldrill(src)
new /obj/item/cautery(src)
+ new /obj/item/bonesetter(src)
new /obj/item/surgical_drapes(src)
new /obj/item/clothing/mask/surgical(src)
new /obj/item/reagent_containers/medspray/sterilizine(src)
@@ -391,6 +392,7 @@
new /obj/item/circular_saw(src)
new /obj/item/surgicaldrill(src)
new /obj/item/cautery(src)
+ new /obj/item/bonesetter(src)
new /obj/item/surgical_drapes(src)
new /obj/item/clothing/mask/surgical(src)
new /obj/item/reagent_containers/medspray/sterilizine(src)
@@ -485,6 +487,7 @@
new /obj/item/circular_saw(src)
new /obj/item/surgicaldrill(src)
new /obj/item/cautery(src)
+ new /obj/item/bonesetter(src)
new /obj/item/surgical_drapes(src)
new /obj/item/clothing/suit/straight_jacket(src)
new /obj/item/clothing/mask/muzzle(src)
@@ -501,8 +504,8 @@
new /obj/item/scalpel/advanced(src)
new /obj/item/retractor/advanced(src)
new /obj/item/surgicaldrill/advanced(src)
+ new /obj/item/bonesetter(src)
new /obj/item/surgical_drapes(src)
- new /obj/item/storage/firstaid/tactical(src)
new /obj/item/clothing/suit/straight_jacket(src)
new /obj/item/clothing/mask/muzzle(src)
new /obj/item/mmi/syndie(src)
@@ -648,3 +651,9 @@ obj/item/storage/backpack/duffelbag/syndie/shredderbundle
new /obj/item/gun/ballistic/automatic/flechette/shredder(src)
new /obj/item/storage/belt/military(src)
new /obj/item/clothing/suit/space/hardsuit/syndi/elite(src)
+
+/obj/item/storage/backpack/snail
+ name = "snail shell"
+ desc = "Worn by snails as armor and storage compartment."
+ icon_state = "snailshell"
+ item_state = "snailshell"
diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm
index 2831311eff..b64aa60cac 100644
--- a/code/game/objects/items/storage/bags.dm
+++ b/code/game/objects/items/storage/bags.dm
@@ -48,7 +48,8 @@
STR.max_w_class = WEIGHT_CLASS_SMALL
STR.max_combined_w_class = 30
STR.max_items = 30
- STR.cant_hold = typecacheof(list(/obj/item/disk/nuclear))
+ STR.can_hold_extra = typecacheof(list(/obj/item/organ/lungs, /obj/item/organ/liver, /obj/item/organ/stomach, /obj/item/clothing/shoes)) - typesof(/obj/item/clothing/shoes/magboots, /obj/item/clothing/shoes/clown_shoes, /obj/item/clothing/shoes/jackboots, /obj/item/clothing/shoes/workboots)
+ STR.cant_hold = typecacheof(list(/obj/item/disk/nuclear, /obj/item/storage/wallet, /obj/item/organ/brain))
STR.limited_random_access = TRUE
STR.limited_random_access_stack_position = 3
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index 4fa742df46..3c554aa58e 100755
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -83,7 +83,7 @@
new /obj/item/multitool(src)
new /obj/item/stack/cable_coil(src,30,pick("red","yellow","orange"))
new /obj/item/extinguisher/mini(src)
- new /obj/item/analyzer(src)
+ new /obj/item/analyzer/ranged(src)
//much roomier now that we've managed to remove two tools
/obj/item/storage/belt/utility/full/PopulateContents()
@@ -120,7 +120,7 @@
new /obj/item/wrench/brass(src)
new /obj/item/crowbar/brass(src)
new /obj/item/weldingtool/experimental/brass(src)
- new /obj/item/multitool(src)
+ new /obj/item/multitool/advanced/brass(src)
new /obj/item/stack/cable_coil(src, 30, "yellow")
/obj/item/storage/belt/medical
@@ -162,6 +162,7 @@
/obj/item/surgical_drapes, //for true paramedics
/obj/item/scalpel,
/obj/item/circular_saw,
+ /obj/item/bonesetter,
/obj/item/surgicaldrill,
/obj/item/retractor,
/obj/item/cautery,
@@ -181,7 +182,8 @@
/obj/item/implant,
/obj/item/implanter,
/obj/item/pinpointer/crew,
- /obj/item/reagent_containers/chem_pack
+ /obj/item/reagent_containers/chem_pack,
+ /obj/item/stack/sticky_tape //surgical tape
))
/obj/item/storage/belt/medical/surgery_belt_adv
@@ -444,10 +446,11 @@
/obj/item/storage/belt/durathread
name = "durathread toolbelt"
- desc = "A toolbelt made out of durathread, it seems resistant enough to hold even big tools like an RCD, it also has higher capacity."
+ desc = "A toolbelt made out of durathread, it seems robust enough to hold bigger tools like RCDs or RPDs, with enough pouches to hold more gear than a normal belt."
icon_state = "webbing-durathread"
item_state = "webbing-durathread"
resistance_flags = FIRE_PROOF
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //If normal belts get this, the upgraded version should too
/obj/item/storage/belt/durathread/ComponentInitialize()
. = ..()
@@ -467,7 +470,7 @@
/obj/item/t_scanner,
/obj/item/analyzer,
/obj/item/geiger_counter,
- /obj/item/extinguisher/mini,
+ /obj/item/extinguisher,
/obj/item/radio,
/obj/item/clothing/gloves,
/obj/item/holosign_creator,
@@ -488,7 +491,8 @@
desc = "A belt for holding grenades."
icon_state = "grenadebeltnew"
item_state = "security"
-
+ rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
+
/obj/item/storage/belt/grenade/ComponentInitialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
@@ -811,4 +815,3 @@
attack_verb = list("bashed", "slashes", "prods", "pokes")
fitting_swords = list(/obj/item/melee/rapier)
starting_sword = /obj/item/melee/rapier
-
diff --git a/code/game/objects/items/storage/book.dm b/code/game/objects/items/storage/book.dm
index 5d5f0eaa24..7532aacbfa 100644
--- a/code/game/objects/items/storage/book.dm
+++ b/code/game/objects/items/storage/book.dm
@@ -104,7 +104,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "bible",
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "blessing", /datum/mood_event/blessing)
return 1
-/obj/item/storage/book/bible/attack(mob/living/M, mob/living/carbon/human/user, heal_mode = TRUE)
+/obj/item/storage/book/bible/attack(mob/living/M, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1, heal_mode = TRUE)
if (!user.IsAdvancedToolUser())
to_chat(user, "You don't have the dexterity to do this!")
@@ -238,11 +238,10 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "bible",
var/ownername = H.real_name
desc += "The name [ownername] is written in blood inside the cover."
-/obj/item/storage/book/bible/syndicate/attack(mob/living/M, mob/living/carbon/human/user, heal_mode = TRUE)
- if (user.a_intent == INTENT_HELP)
- return ..()
- else
- return ..(M,user,heal_mode = FALSE)
+/obj/item/storage/book/bible/syndicate/attack(mob/living/M, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1, heal_mode = TRUE)
+ if(user.a_intent != INTENT_HELP)
+ heal_mode = FALSE //args pass over
+ return ..() // to ..()
/obj/item/storage/book/bible/syndicate/add_blood_DNA(list/blood_dna)
return FALSE
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index cdd3781748..975621ead1 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -726,9 +726,9 @@ obj/item/storage/box/stingbangs
return (BRUTELOSS)
/obj/item/storage/box/hug/attack_self(mob/user)
- ..()
- user.changeNext_move(CLICK_CD_MELEE)
- playsound(loc, "rustle", 50, 1, -5)
+ . = ..()
+ user.DelayNextAction(CLICK_CD_MELEE)
+ playsound(src, "rustle", 50, 1, -5)
user.visible_message("[user] hugs \the [src].","You hug \the [src].")
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT,"hugbox", /datum/mood_event/hugbox)
@@ -1418,3 +1418,9 @@ obj/item/storage/box/stingbangs
new /obj/item/reagent_containers/glass/beaker/meta(src)
new /obj/item/reagent_containers/glass/beaker/noreact(src)
new /obj/item/reagent_containers/glass/beaker/bluespace(src)
+
+/obj/item/storage/box/strange_seeds_5pack
+
+/obj/item/storage/box/strange_seeds_5pack/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/seeds/random(src)
diff --git a/code/game/objects/items/storage/dakis.dm b/code/game/objects/items/storage/dakis.dm
index 1939593c8e..29f12df4c4 100644
--- a/code/game/objects/items/storage/dakis.dm
+++ b/code/game/objects/items/storage/dakis.dm
@@ -47,6 +47,6 @@
if(INTENT_HARM)
user.visible_message("[user] punches the [name]!")
playsound(src, 'sound/effects/shieldbash.ogg', 50, 1)
- user.changeNext_move(CLICK_CD_MELEE)
+ user.DelayNextAction(CLICK_CD_MELEE)
////////////////////////////
diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm
index 927a29407c..cde6f5bd5a 100644
--- a/code/game/objects/items/storage/fancy.dm
+++ b/code/game/objects/items/storage/fancy.dm
@@ -168,13 +168,19 @@
. += "There's a coupon on the back of the pack! You can tear it off once it's empty."
/obj/item/storage/fancy/cigarettes/AltClick(mob/living/carbon/user)
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
- var/obj/item/clothing/mask/cigarette/W = locate(/obj/item/clothing/mask/cigarette) in contents
+ var/obj/item/lighter/L = locate() in contents
+ if(L)
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, L, user)
+ user.put_in_hands(L)
+ to_chat(user, "You take \a [L] out of the pack.")
+ return TRUE
+ var/obj/item/clothing/mask/cigarette/W = locate() in contents
if(W && contents.len > 0)
SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user)
user.put_in_hands(W)
- contents -= W
to_chat(user, "You take \a [W] out of the pack.")
else
to_chat(user, "There are no [icon_type]s left in the pack.")
@@ -398,6 +404,20 @@
STR.max_items = 6
STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/nugget))
+/obj/item/storage/fancy/cracker_pack
+ name = "cracker pack"
+ desc = "A pack of delicious crackers. Keep away from parrots!"
+ icon = 'icons/obj/food/containers.dmi'
+ icon_state = "crackerbox"
+ icon_type = "cracker"
+ spawn_type = /obj/item/reagent_containers/food/snacks/cracker
+
+/obj/item/storage/fancy/cracker_pack/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ STR.max_items = 6
+ STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/cracker))
+
/*
* Ring Box
*/
diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm
index d7065df0f0..480e1966e9 100644
--- a/code/game/objects/items/storage/firstaid.dm
+++ b/code/game/objects/items/storage/firstaid.dm
@@ -41,9 +41,25 @@
new /obj/item/stack/medical/suture(src)
new /obj/item/stack/medical/mesh(src)
new /obj/item/stack/medical/mesh(src)
- new /obj/item/reagent_containers/hypospray/medipen(src)
+ new /obj/item/reagent_containers/hypospray/medipen/ekit(src)
new /obj/item/healthanalyzer(src)
+/obj/item/storage/firstaid/emergency
+ icon_state = "medbriefcase"
+ name = "emergency first-aid kit"
+ desc = "A very simple first aid kit meant to secure and stabilize serious wounds for later treatment."
+
+/obj/item/storage/firstaid/emergency/PopulateContents()
+ if(empty)
+ return
+ var/static/items_inside = list(
+ /obj/item/healthanalyzer/wound = 1,
+ /obj/item/stack/medical/gauze = 1,
+ /obj/item/stack/medical/suture/emergency = 1,
+ /obj/item/stack/medical/ointment = 1,
+ /obj/item/reagent_containers/hypospray/medipen/ekit = 2)
+ generate_items_inside(items_inside,src)
+
/obj/item/storage/firstaid/ancient
icon_state = "firstaid"
desc = "A first aid kit with the ability to heal common types of injuries."
@@ -229,6 +245,25 @@
STR.click_gather = TRUE
STR.can_hold = typecacheof(list(/obj/item/reagent_containers/pill, /obj/item/dice))
+/obj/item/storage/pill_bottle/AltClick(mob/living/carbon/user)
+ if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
+ return
+ if(!length(user.get_empty_held_indexes()))
+ to_chat(user, "Your hands are full!")
+ return
+ var/obj/item/reagent_containers/pill/P = locate() in contents
+ if(P)
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, P, user)
+ if(!user.put_in_hands(P))
+ P.forceMove(user.drop_location()) // make sure it's not stuck in the user if the put in hands somehow fails
+ to_chat(user, "[P] drops to the floor!")
+ else
+ to_chat(user, "You take \a [P] out of [src].")
+ else
+ to_chat(user, "There are no pills left in the bottle.")
+ return TRUE
+
+
/obj/item/storage/pill_bottle/suicide_act(mob/user)
user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!")
return (TOXLOSS)
@@ -416,6 +451,7 @@
/obj/item/retractor,
/obj/item/cautery,
/obj/item/surgical_drapes,
+ /obj/item/bonesetter,
/obj/item/autosurgeon,
/obj/item/organ,
/obj/item/implant,
diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm
index 0e9a9fda61..4e6a0b3463 100644
--- a/code/game/objects/items/storage/secure.dm
+++ b/code/game/objects/items/storage/secure.dm
@@ -205,10 +205,7 @@
new /obj/item/paper(src)
new /obj/item/pen(src)
-/obj/item/storage/secure/safe/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/item/storage/secure/safe/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
return attack_self(user)
/obj/item/storage/secure/safe/HoS
diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm
index fd50bd022f..ed0ee8ad07 100644
--- a/code/game/objects/items/storage/toolbox.dm
+++ b/code/game/objects/items/storage/toolbox.dm
@@ -20,6 +20,8 @@ GLOBAL_LIST_EMPTY(rubber_toolbox_icons)
var/has_latches = TRUE
var/can_rubberify = TRUE
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //very protecc too
+ wound_bonus = -10
+ bare_wound_bonus = 5
/obj/item/storage/toolbox/greyscale
icon_state = "toolbox_default"
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 3b66e32d0a..442228ef31 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -169,6 +169,14 @@
O.imp = new /obj/item/implant/freedom(O)
O.update_icon()
+/obj/item/storage/box/syndie_kit/imp_warp
+ name = "boxed warp implant (with injector)"
+
+/obj/item/storage/box/syndie_kit/imp_warp/PopulateContents()
+ var/obj/item/implanter/O = new(src)
+ O.imp = new /obj/item/implant/warp(O)
+ O.update_icon()
+
/obj/item/storage/box/syndie_kit/imp_microbomb
name = "Microbomb Implant (with injector)"
@@ -327,6 +335,7 @@
new /obj/item/reagent_containers/glass/bottle/romerol(src)
new /obj/item/reagent_containers/syringe(src)
new /obj/item/reagent_containers/dropper(src)
+ new /obj/item/paper/guides/antag/romerol_instructions(src)
/obj/item/storage/box/syndie_kit/ez_clean/PopulateContents()
for(var/i in 1 to 3)
@@ -401,7 +410,7 @@
/obj/item/storage/box/syndie_kit/revolver
/obj/item/storage/box/syndie_kit/revolver/PopulateContents()
- new /obj/item/gun/ballistic/revolver(src)
+ new /obj/item/gun/ballistic/revolver/syndicate(src)
new /obj/item/ammo_box/a357(src)
/obj/item/storage/box/syndie_kit/pistol
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index fa0c9ba693..1a84b85a1a 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -14,6 +14,7 @@
w_class = WEIGHT_CLASS_NORMAL
attack_verb = list("beaten")
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
+ attack_speed = CLICK_CD_MELEE
var/stamforce = 35
var/turned_on = FALSE
@@ -144,8 +145,9 @@
return ..()
/obj/item/melee/baton/alt_pre_attack(atom/A, mob/living/user, params)
+ if(!user.CheckActionCooldown(CLICK_CD_MELEE))
+ return
. = common_baton_melee(A, user, TRUE) //return true (attackchain interrupt) if this also returns true. no harm-disarming.
- user.changeNext_move(CLICK_CD_MELEE)
//return TRUE to interrupt attack chain.
/obj/item/melee/baton/proc/common_baton_melee(mob/M, mob/living/user, disarming = FALSE)
@@ -156,6 +158,7 @@
if(IS_STAMCRIT(user)) //CIT CHANGE - makes it impossible to baton in stamina softcrit
to_chat(user, "You're too exhausted to use [src] properly.")
return TRUE
+ user.DelayNextAction()
if(ishuman(M))
var/mob/living/carbon/human/L = M
if(check_martial_counter(L, user))
diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm
index e69b2b6272..08b91332ef 100644
--- a/code/game/objects/items/tanks/tanks.dm
+++ b/code/game/objects/items/tanks/tanks.dm
@@ -123,6 +123,7 @@
/obj/item/tank/analyzer_act(mob/living/user, obj/item/I)
atmosanalyzer_scan(air_contents, user, src)
+ return TRUE
/obj/item/tank/deconstruct(disassembled = TRUE)
if(!disassembled)
@@ -143,7 +144,6 @@
if(prob(50))
step(W, pick(GLOB.alldirs))
ADD_TRAIT(H, TRAIT_DISFIGURED, TRAIT_GENERIC)
- H.bleed_rate = 5
H.gib_animation()
sleep(3)
H.adjustBruteLoss(1000) //to make the body super-bloody
@@ -164,11 +164,13 @@
else
. = ..()
-/obj/item/tank/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/item/tank/ui_state(mob/user)
+ return GLOB.hands_state
+
+/obj/item/tank/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "tanks", name, 400, 120, master_ui, state)
+ ui = new(user, src, "Tank", name)
ui.open()
/obj/item/tank/ui_data(mob/user)
@@ -205,10 +207,6 @@
else if(pressure == "max")
pressure = TANK_MAX_RELEASE_PRESSURE
. = TRUE
- else if(pressure == "input")
- pressure = input("New release pressure ([TANK_MIN_RELEASE_PRESSURE]-[TANK_MAX_RELEASE_PRESSURE] kPa):", name, distribute_pressure) as num|null
- if(!isnull(pressure) && !..())
- . = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm
index b1c0d3dfbb..16a91b6358 100644
--- a/code/game/objects/items/tanks/watertank.dm
+++ b/code/game/objects/items/tanks/watertank.dm
@@ -72,7 +72,7 @@
QDEL_NULL(noz)
return ..()
-/obj/item/watertank/attack_hand(mob/user)
+/obj/item/watertank/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if (user.get_item_by_slot(user.getBackSlot()) == src)
toggle_mister(user)
else
diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm
index 881e4d5f1a..f9ede407ca 100644
--- a/code/game/objects/items/teleportation.dm
+++ b/code/game/objects/items/teleportation.dm
@@ -1,4 +1,3 @@
-
#define SOURCE_PORTAL 1
#define DESTINATION_PORTAL 2
@@ -25,90 +24,72 @@
throw_speed = 3
throw_range = 7
custom_materials = list(/datum/material/iron=400)
+ var/tracking_range = 20
-/obj/item/locator/attack_self(mob/user)
- user.set_machine(src)
- var/dat
- if (temp)
- dat = "[temp]
Clear"
- else
- dat = {"
-Persistent Signal Locator
-Refresh"}
- user << browse(dat, "window=radio")
- onclose(user, "radio")
- return
+/obj/item/locator/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "BluespaceLocator", name)
+ ui.open()
-/obj/item/locator/Topic(href, href_list)
- ..()
- if (usr.stat || usr.restrained())
- return
- var/turf/current_location = get_turf(usr)//What turf is the user on?
- if(!current_location || is_centcom_level(current_location.z))//If turf was not found or they're on CentCom
- to_chat(usr, "[src] is malfunctioning.")
- return
- if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)))
- usr.set_machine(src)
- if (href_list["refresh"])
- temp = "Persistent Signal Locator"
- var/turf/sr = get_turf(src)
+/obj/item/locator/ui_data(mob/user)
+ var/list/data = list()
- if (sr)
- temp += "Beacon Signals: "
- for(var/obj/item/beacon/W in GLOB.teleportbeacons)
- if (!W.renamed)
- continue
- var/turf/tr = get_turf(W)
- if (tr.z == sr.z && tr)
- var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
- if (direct < 5)
- direct = "very strong"
- else
- if (direct < 10)
- direct = "strong"
- else
- if (direct < 20)
- direct = "weak"
- else
- direct = "very weak"
- temp += "[W.name]-[dir2text(get_dir(sr, tr))]-[direct] "
+ data["trackingrange"] = tracking_range
- temp += "Implant Signals: "
- for (var/obj/item/implant/tracking/W in GLOB.tracked_implants)
- if (!isliving(W.imp_in))
- continue
- var/mob/living/M = W.imp_in
- if (M.stat == DEAD)
- if (M.timeofdeath + W.lifespan_postmortem < world.time)
- continue
+ // Get our current turf location.
+ var/turf/sr = get_turf(src)
- var/turf/tr = get_turf(M)
- if (tr.z == sr.z && tr)
- var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
- if (direct < 20)
- if (direct < 5)
- direct = "very strong"
- else
- if (direct < 10)
- direct = "strong"
- else
- direct = "weak"
- temp += "[W.imp_in.name]-[dir2text(get_dir(sr, tr))]-[direct] "
+ if (sr)
+ // Check every teleport beacon.
+ var/list/tele_beacons = list()
+ for(var/obj/item/beacon/W in GLOB.teleportbeacons)
- temp += "You are at \[[sr.x],[sr.y],[sr.z]\] in orbital coordinates.
Refresh "
+ // Get the tracking beacon's turf location.
+ var/turf/tr = get_turf(W)
+
+ // Make sure it's on a turf and that its Z-level matches the tracker's Z-level
+ if (tr && tr.z == sr.z)
+ // Get the distance between the beacon's turf and our turf
+ var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
+
+ // If the target is too far away, skip over this beacon.
+ if(distance > tracking_range)
+ continue
+
+ var/beacon_name
+
+ if(W.renamed)
+ beacon_name = W.name
+ else
+ var/area/A = get_area(W)
+ beacon_name = A.name
+
+ var/D = dir2text(get_dir(sr, tr))
+ tele_beacons += list(list(name = beacon_name, direction = D, distance = distance))
+
+ data["telebeacons"] = tele_beacons
+
+ var/list/track_implants = list()
+
+ for (var/obj/item/implant/tracking/W in GLOB.tracked_implants)
+ if (!W.imp_in || !isliving(W.loc))
+ continue
else
- temp += "Processing Error: Unable to locate orbital position. "
- else
- if (href_list["temp"])
- temp = null
- if (ismob(src.loc))
- attack_self(src.loc)
- else
- for(var/mob/M in fov_viewers(1, src))
- if (M.client)
- src.attack_self(M)
- return
+ var/mob/living/M = W.loc
+ if (M.stat == DEAD)
+ if (M.timeofdeath + W.lifespan_postmortem < world.time)
+ continue
+ var/turf/tr = get_turf(W)
+ var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
+ if(distance > tracking_range)
+ continue
+
+ var/D = dir2text(get_dir(sr, tr))
+ track_implants += list(list(name = W.imp_in.name, direction = D, distance = distance))
+ data["trackimplants"] = track_implants
+ return data
/*
* Hand-tele
@@ -190,13 +171,8 @@
user.show_message("\The [src] is recharging!")
return
var/atom/T = L[t1]
- var/implantcheckmate = FALSE
- if(isliving(T))
- var/mob/living/M = T
- if(!locate(/obj/item/implant/tracking) in M.implants) //The user was too slow and let the target mob's tracking implant expire or get removed.
- implantcheckmate = TRUE
var/area/A = get_area(T)
- if(A.noteleport || implantcheckmate)
+ if(A.noteleport)
to_chat(user, "\The [src] is malfunctioning.")
return
current_location = get_turf(user) //Recheck.
@@ -205,10 +181,12 @@
to_chat(user, "\The [src] is malfunctioning.")
return
user.show_message("Locked In.", MSG_AUDIBLE)
- var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), src, 300, 1, null, atmos_link_override)
+ var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), 300, 1, null, atmos_link_override)
if(!(LAZYLEN(created) == 2))
return
- try_move_adjacent(created[1])
+ RegisterSignal(created[1], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy) //Gosh darn it kevinz.
+ RegisterSignal(created[2], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy)
+ try_move_adjacent(created[1], user.dir)
active_portal_pairs[created[1]] = created[2]
var/obj/effect/portal/c1 = created[1]
var/obj/effect/portal/c2 = created[2]
diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm
index 91e8c49e5a..cf1b0cfff7 100644
--- a/code/game/objects/items/tools/crowbar.dm
+++ b/code/game/objects/items/tools/crowbar.dm
@@ -18,6 +18,9 @@
toolspeed = 1
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ wound_bonus = -10
+ bare_wound_bonus = 5
+
/obj/item/crowbar/suicide_act(mob/user)
user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
playsound(loc, 'sound/weapons/genhit.ogg', 50, 1, -1)
@@ -74,8 +77,8 @@
desc = "A set of jaws of life, compressed through the magic of science. It's fitted with a prying head."
icon_state = "jaws_pry"
item_state = "jawsoflife"
- lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ lefthand_file = 'sandcode/icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'sandcode/icons/mob/inhands/equipment/tools_righthand.dmi'
custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25)
usesound = 'sound/items/jaws_pry.ogg'
@@ -90,6 +93,7 @@
/obj/item/crowbar/power/attack_self(mob/user)
playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1)
var/obj/item/wirecutters/power/cutjaws = new /obj/item/wirecutters/power(drop_location())
+ cutjaws.name = name
to_chat(user, "You attach the cutting jaws to [src].")
qdel(src)
user.put_in_active_hand(cutjaws)
@@ -100,4 +104,4 @@
icon = 'icons/obj/advancedtools.dmi'
usesound = 'sound/weapons/sonic_jackhammer.ogg'
icon_state = "crowbar"
- toolspeed = 0.2
\ No newline at end of file
+ toolspeed = 0.2
diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm
index e9f517a9f1..1ce741bef5 100644
--- a/code/game/objects/items/tools/screwdriver.dm
+++ b/code/game/objects/items/tools/screwdriver.dm
@@ -31,6 +31,9 @@
"yellow" = rgb(255, 165, 0)
)
+ wound_bonus = -10
+ bare_wound_bonus = 5
+
/obj/item/screwdriver/suicide_act(mob/user)
user.visible_message("[user] is stabbing [src] into [user.p_their()] [pick("temple", "heart")]! It looks like [user.p_theyre()] trying to commit suicide!")
return(BRUTELOSS)
@@ -112,8 +115,8 @@
desc = "A simple powered hand drill. It's fitted with a screw bit."
icon_state = "drill_screw"
item_state = "drill"
- lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ lefthand_file = 'sandcode/icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'sandcode/icons/mob/inhands/equipment/tools_righthand.dmi'
custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) //done for balance reasons, making them high value for research, but harder to get
force = 8 //might or might not be too high, subject to change
w_class = WEIGHT_CLASS_SMALL
@@ -155,4 +158,4 @@
item_state = "screwdriver_nuke"
usesound = 'sound/items/pshoom.ogg'
toolspeed = 0.2
- random_color = FALSE
\ No newline at end of file
+ random_color = FALSE
diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm
index 9560f7fab3..2e12a2ab8c 100644
--- a/code/game/objects/items/tools/weldingtool.dm
+++ b/code/game/objects/items/tools/weldingtool.dm
@@ -33,6 +33,8 @@
heat = 3800
tool_behaviour = TOOL_WELDER
toolspeed = 1
+ wound_bonus = 0
+ bare_wound_bonus = 5
/obj/item/weldingtool/Initialize()
. = ..()
diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm
index ac5a02b9fc..25443132c5 100644
--- a/code/game/objects/items/tools/wirecutters.dm
+++ b/code/game/objects/items/tools/wirecutters.dm
@@ -97,8 +97,10 @@
desc = "A set of jaws of life, compressed through the magic of science. It's fitted with a cutting head."
icon_state = "jaws_cutter"
item_state = "jawsoflife"
-
+ lefthand_file = 'sandcode/icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'sandcode/icons/mob/inhands/equipment/tools_righthand.dmi'
custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25)
+
usesound = 'sound/items/jaws_cut.ogg'
toolspeed = 0.25
random_color = FALSE
@@ -117,6 +119,7 @@
/obj/item/wirecutters/power/attack_self(mob/user)
playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1)
var/obj/item/crowbar/power/pryjaws = new /obj/item/crowbar/power(drop_location())
+ pryjaws.name = name
to_chat(user, "You attach the pry jaws to [src].")
qdel(src)
user.put_in_active_hand(pryjaws)
@@ -144,4 +147,4 @@
icon = 'icons/obj/advancedtools.dmi'
icon_state = "cutters"
toolspeed = 0.2
- random_color = FALSE
\ No newline at end of file
+ random_color = FALSE
diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm
index 678096db28..1a4a18ae4c 100644
--- a/code/game/objects/items/tools/wrench.dm
+++ b/code/game/objects/items/tools/wrench.dm
@@ -18,6 +18,9 @@
toolspeed = 1
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ wound_bonus = -10
+ bare_wound_bonus = 5
+
/obj/item/wrench/suicide_act(mob/user)
user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
playsound(loc, 'sound/weapons/genhit.ogg', 50, 1, -1)
@@ -56,8 +59,8 @@
desc = "A simple powered hand drill. It's fitted with a bolt bit."
icon_state = "drill_bolt"
item_state = "drill"
- lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ lefthand_file = 'sandcode/icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'sandcode/icons/mob/inhands/equipment/tools_righthand.dmi'
usesound = 'sound/items/drill_use.ogg'
custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25)
//done for balance reasons, making them high value for research, but harder to get
@@ -122,4 +125,4 @@
icon = 'icons/obj/advancedtools.dmi'
icon_state = "wrench"
usesound = 'sound/effects/empulse.ogg'
- toolspeed = 0.2
\ No newline at end of file
+ toolspeed = 0.2
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index bf1eca01f9..4e82df2452 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -447,7 +447,7 @@
block_parry_data = null
attack_verb = list("attacked", "struck", "hit")
total_mass_on = TOTAL_MASS_TOY_SWORD
- sharpness = IS_BLUNT
+ sharpness = SHARP_NONE
/obj/item/dualsaber/toy/ComponentInitialize()
AddComponent(/datum/component/two_handed, force_unwielded=0, force_wielded=0, wieldsound='sound/weapons/saberon.ogg', unwieldsound='sound/weapons/saberoff.ogg')
@@ -466,7 +466,7 @@
attack_verb = list("attacked", "struck", "hit")
total_mass_on = TOTAL_MASS_TOY_SWORD
slowdown_wielded = 0
- sharpness = IS_BLUNT
+ sharpness = SHARP_NONE
/obj/item/dualsaber/hypereutactic/toy/ComponentInitialize()
AddComponent(/datum/component/two_handed, force_unwielded=0, force_wielded=0, wieldsound='sound/weapons/saberon.ogg', unwieldsound='sound/weapons/saberoff.ogg')
@@ -574,10 +574,7 @@
else
. = ..()
-/obj/item/toy/prize/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/item/toy/prize/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user)
attack_self(user)
@@ -811,9 +808,8 @@
cards += "Ace of Clubs"
cards += "Ace of Diamonds"
-//ATTACK HAND IGNORING PARENT RETURN VALUE
//ATTACK HAND NOT CALLING PARENT
-/obj/item/toy/cards/deck/attack_hand(mob/user)
+/obj/item/toy/cards/deck/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
draw_card(user)
/obj/item/toy/cards/deck/proc/draw_card(mob/user)
@@ -1234,7 +1230,7 @@
name = "steampunk watch"
desc = "A stylish steampunk watch made out of thousands of tiny cogwheels."
icon = 'icons/obj/clockwork_objects.dmi'
- icon_state = "dread_ipad"
+ icon_state = "clockwork_slab"
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_SMALL
var/cooldown = 0
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index 4c6bcc08b4..2109d1038e 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -35,6 +35,10 @@
name = "syndi-cakes"
icon_state = "syndi_cakes"
+/obj/item/trash/energybar
+ name = "energybar wrapper"
+ icon_state = "energybar"
+
/obj/item/trash/waffles
name = "waffles tray"
icon_state = "waffles"
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 7524bb93e1..e7dc636cf3 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -65,7 +65,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
w_class = WEIGHT_CLASS_NORMAL
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
block_chance = 50
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
resistance_flags = FIRE_PROOF
@@ -231,7 +231,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
block_chance = 50
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
resistance_flags = FIRE_PROOF
@@ -249,6 +249,174 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
playsound(src, 'sound/weapons/bladeslice.ogg', 50, 1)
return(BRUTELOSS)
+/obj/item/katana/timestop
+ name = "temporal katana"
+ desc = "Delicately balanced, this finely-crafted blade hums with barely-restrained potential."
+ block_chance = 0 // oops
+ force = 27.5 // oops
+ item_flags = ITEM_CAN_PARRY
+ block_parry_data = /datum/block_parry_data/bokken/quick_parry/proj
+
+/obj/item/katana/timestop/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time)
+ if(ishuman(owner))
+ var/mob/living/carbon/human/flynn = owner
+ flynn.emote("smirk")
+ new /obj/effect/timestop/magic(get_turf(owner), 1, 50, list(owner)) // null roddies counter
+
+/obj/item/melee/bokken // parrying stick
+ name = "bokken"
+ desc = "A space-Japanese training sword made of wood and shaped like a katana."
+ icon_state = "bokken"
+ item_state = "bokken"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK
+ w_class = WEIGHT_CLASS_BULKY
+ force = 9
+ throwforce = 10
+ damtype = STAMINA
+ attack_verb = list("whacked", "smacked", "struck")
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
+ hitsound = 'sound/weapons/grenadelaunch.ogg' // no good wood thunk sounds
+ var/harm = FALSE // TRUE = brute, FALSE = stam
+ var/reinforced = FALSE
+ var/burnt = FALSE
+ var/burned_in // text you burned in (with a welder)
+ var/quick_parry = FALSE // false = default parry, true = really small parry window
+ item_flags = ITEM_CAN_PARRY
+ block_parry_data = /datum/block_parry_data/bokken
+ bare_wound_bonus = 0
+ wound_bonus = 0
+
+/datum/block_parry_data/bokken // fucked up parry data, emphasizing quicker, shorter parries
+ parry_stamina_cost = 8 // be wise about when you parry, though, else you won't be able to fight enough to make it count
+ parry_time_windup = 0
+ parry_time_active = 10 // small parry window
+ parry_time_spindown = 0
+ // parry_flags = PARRY_DEFAULT_HANDLE_FEEDBACK // bokken users can no longer strike while parrying
+ parry_time_perfect = 1.5
+ parry_time_perfect_leeway = 1
+ parry_imperfect_falloff_percent = 7.5
+ parry_efficiency_to_counterattack = 100
+ parry_efficiency_considered_successful = 65 // VERY generous
+ parry_efficiency_perfect = 120
+ parry_efficiency_perfect_override = list(
+ TEXT_ATTACK_TYPE_PROJECTILE = 30,
+ )
+ parry_failed_stagger_duration = 3 SECONDS
+ parry_data = list(
+ PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN = 2.5, // 7*2.5 = 17.5, 8*2.5 = 20, 9*2.5 = 22.5, 10*2.5 = 25
+ )
+
+/datum/block_parry_data/bokken/quick_parry // emphasizing REALLY SHORT PARRIES
+ parry_stamina_cost = 6 // still more costly than most parries, but less than a full bokken parry
+ parry_time_active = 5 // REALLY small parry window
+ parry_time_perfect = 2.5 // however...
+ parry_time_perfect_leeway = 2.5 // the entire time, the parry is perfect
+ parry_failed_stagger_duration = 1 SECONDS
+ parry_failed_clickcd_duration = 1 SECONDS // more forgiving punishments for missed parries
+ // still, don't fucking miss your parries or you're down stamina and staggered to shit
+
+/datum/block_parry_data/bokken/quick_parry/proj
+ parry_efficiency_perfect_override = list()
+
+/obj/item/melee/bokken/Initialize()
+ . = ..()
+ AddElement(/datum/element/sword_point)
+
+/obj/item/melee/bokken/attack_self(mob/user)
+ harm = !harm
+ if(harm)
+ force -= 2
+ damtype = BRUTE
+ attack_verb = list("bashed", "smashed", "attacked")
+ bare_wound_bonus = 15 // having your leg smacked by a wooden stick is probably not great for it if it's naked
+ wound_bonus = 0
+ else
+ force += 2
+ damtype = STAMINA
+ attack_verb = list("whacked", "smacked", "struck")
+ bare_wound_bonus = 0
+ wound_bonus = 0
+ to_chat(user, "[src] is now [harm ? "harmful" : "not quite as harmful"].")
+
+/obj/item/melee/bokken/AltClick(mob/user)
+ . = ..()
+ quick_parry = !quick_parry
+ if(quick_parry)
+ block_parry_data = /datum/block_parry_data/bokken/quick_parry
+ else
+ block_parry_data = /datum/block_parry_data/bokken
+ to_chat(user, "[src] is now [quick_parry ? "emphasizing shorter parries, forcing you to riposte or be staggered" : "emphasizing longer parries, with a shorter window to riposte but more forgiving parries"].")
+
+/obj/item/melee/bokken/attackby(obj/item/I, mob/living/user, params)
+ if(istype(I, /obj/item/pen))
+ var/new_name = stripped_input(user, "What do you wish to name [src]?", "New Name", "bokken", 30)
+ if(new_name)
+ name = new_name
+ if(I.tool_behaviour == TOOL_WELDER)
+ var/new_burn = stripped_input(user, "What do you wish to burn into [src]?", "Burnt Inscription","", 140)
+ if(new_burn)
+ burned_in = new_burn
+ if(!burnt)
+ icon_state += "_burnt"
+ item_state += "_burnt"
+ burnt = TRUE
+ update_icon()
+ update_icon_state()
+ if(istype(I, /obj/item/stack/rods))
+ var/obj/item/stack/rods/R = I
+ if(!reinforced)
+ if(R.use(1))
+ force++
+ reinforced = TRUE
+ to_chat(user, "You slide a metal rod into [src]\'s hilt. It feels a little heftier in your hands.")
+ else
+ to_chat(user, "[src] already has a weight slid into the hilt.")
+
+/obj/item/melee/bokken/examine(mob/user)
+ . = ..()
+ if(quick_parry)
+ . += " [src] is gripped in a way to emphasize quicker parries."
+ if(reinforced)
+ . += " There's a metal rod shoved into the base."
+ if(burnt)
+ . += " Burned into the \"blade\" is [burned_in]."
+
+/obj/item/melee/bokken/debug
+ name = "funny debug parrying stick"
+ desc = "if you see this you've fucked up somewhere my good man"
+ block_parry_data = /datum/block_parry_data/bokken/debug
+
+/obj/item/melee/bokken/debug/AltClick(mob/user)
+ quick_parry = !quick_parry
+ if(quick_parry)
+ block_parry_data = /datum/block_parry_data/bokken/quick_parry/debug
+ else
+ block_parry_data = /datum/block_parry_data/bokken/debug
+ to_chat(user, "[src] is now [quick_parry ? "emphasizing shorter parries, forcing you to riposte or be staggered" : "emphasizing longer parries, with a shorter window to riposte but more forgiving parries"].")
+
+/datum/block_parry_data/bokken/debug
+ parry_efficiency_perfect_override = list()
+ parry_data = list(
+ PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN = 2.5, // 7*2.5 = 17.5, 8*2.5 = 20, 9*2.5 = 22.5, 10*2.5 = 25
+ PARRY_DISARM_ATTACKER = TRUE,
+ PARRY_KNOCKDOWN_ATTACKER = 10,
+ PARRY_STAGGER_ATTACKER = 10,
+ PARRY_DAZE_ATTACKER = 10,
+ )
+
+/datum/block_parry_data/bokken/quick_parry/debug
+ parry_efficiency_perfect_override = list()
+ parry_data = list(
+ PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN = 2.5, // 7*2.5 = 17.5, 8*2.5 = 20, 9*2.5 = 22.5, 10*2.5 = 25
+ PARRY_DISARM_ATTACKER = TRUE,
+ PARRY_KNOCKDOWN_ATTACKER = 10,
+ PARRY_STAGGER_ATTACKER = 10,
+ PARRY_DAZE_ATTACKER = 10,
+ )
+
+
/obj/item/wirerod
name = "wired rod"
desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit."
@@ -260,6 +428,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
w_class = WEIGHT_CLASS_NORMAL
custom_materials = list(/datum/material/iron=1150, /datum/material/glass=75)
attack_verb = list("hit", "bludgeoned", "whacked", "bonked")
+ wound_bonus = -10
/obj/item/wirerod/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/shard))
@@ -297,13 +466,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
force = 2
- throwforce = 20 //This is never used on mobs since this has a 100% embed chance.
+ throwforce = 10 //This is never used on mobs since this has a 100% embed chance.
throw_speed = 4
embedding = list("pain_mult" = 4, "embed_chance" = 100, "fall_chance" = 0, "embed_chance_turf_mod" = 15)
armour_penetration = 40
w_class = WEIGHT_CLASS_SMALL
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
custom_materials = list(/datum/material/iron=500, /datum/material/glass=500)
resistance_flags = FIRE_PROOF
@@ -316,7 +485,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/throwing_star/toy
name = "toy throwing star"
desc = "An aerodynamic disc strapped with adhesive for sticking to people, good for playing pranks and getting yourself killed by security."
- sharpness = IS_BLUNT
+ sharpness = SHARP_NONE
force = 0
throwforce = 0
embedding = list("pain_mult" = 0, "jostle_pain_mult" = 0, "embed_chance" = 100, "fall_chance" = 0)
@@ -353,7 +522,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
icon_state = extended_icon_state
attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
else
force = initial(force)
w_class = WEIGHT_CLASS_SMALL
@@ -361,7 +530,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
icon_state = retracted_icon_state
attack_verb = list("stubbed", "poked")
hitsound = 'sound/weapons/genhit.ogg'
- sharpness = IS_BLUNT
+ sharpness = SHARP_NONE
/obj/item/switchblade/suicide_act(mob/user)
user.visible_message("[user] is slitting [user.p_their()] own throat with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -462,7 +631,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
throwforce = 0
throw_range = 0
throw_speed = 0
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
attack_verb = list("sawed", "torn", "cut", "chopped", "diced")
hitsound = 'sound/weapons/chainsawhit.ogg'
total_mass = TOTAL_MASS_HAND_REPLACEMENT
@@ -574,6 +743,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
force = 10
+ wound_bonus = -10
throwforce = 12
attack_verb = list("beat", "smacked")
custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 3.5)
@@ -590,6 +760,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
obj_flags = UNIQUE_RENAME
var/chaplain_spawnable = TRUE
total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
+ wound_bonus = -5
/obj/item/melee/baseball_bat/chaplain/Initialize()
. = ..()
@@ -626,7 +797,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
homerun_ready = 0
return
else if(!target.anchored)
- target.throw_at(throw_target, rand(1,2), 7, user)
+ var/whack_speed = (prob(60) ? 1 : 4)
+ target.throw_at(throw_target, rand(1, 2), whack_speed, user) // sorry friends, 7 speed batting caused wounds to absolutely delete whoever you knocked your target into (and said target)
/obj/item/melee/baseball_bat/ablative
name = "metal baseball bat"
@@ -706,6 +878,92 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
item_flags = DROPDEL | ABSTRACT
attack_verb = list("bopped")
+/obj/item/circlegame/Initialize()
+ . = ..()
+ var/mob/living/owner = loc
+ if(!istype(owner))
+ return
+ RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/ownerExamined)
+
+/obj/item/circlegame/Destroy()
+ var/mob/owner = loc
+ if(istype(owner))
+ UnregisterSignal(owner, COMSIG_PARENT_EXAMINE)
+ return ..()
+
+/obj/item/circlegame/dropped(mob/user)
+ UnregisterSignal(user, COMSIG_PARENT_EXAMINE) //loc will have changed by the time this is called, so Destroy() can't catch it
+ // this is a dropdel item.
+ return ..()
+
+/// Stage 1: The mistake is made
+/obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker)
+ if(!istype(sucker) || !in_range(owner, sucker))
+ return
+ addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4)
+
+/// Stage 2: Fear sets in
+/obj/item/circlegame/proc/waitASecond(mob/living/owner, mob/living/sucker)
+ if(QDELETED(sucker) || QDELETED(src) || QDELETED(owner))
+ return
+
+ if(owner == sucker) // big mood
+ to_chat(owner, "Wait a second... you just looked at your own [src.name]!")
+ addtimer(CALLBACK(src, .proc/selfGottem, owner), 10)
+ else
+ to_chat(sucker, "Wait a second... was that a-")
+ addtimer(CALLBACK(src, .proc/GOTTEM, owner, sucker), 6)
+
+/// Stage 3A: We face our own failures
+/obj/item/circlegame/proc/selfGottem(mob/living/owner)
+ if(QDELETED(src) || QDELETED(owner))
+ return
+
+ playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1)
+ owner.visible_message("[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name].", "You shamefully bop yourself with your [src.name].", \
+ "You hear a dull thud!")
+ log_combat(owner, owner, "bopped", src.name, "(self)")
+ owner.do_attack_animation(owner)
+ owner.apply_damage(100, STAMINA)
+ owner.Knockdown(10)
+ qdel(src)
+
+/// Stage 3B: We face our reckoning (unless we moved away or they're incapacitated)
+/obj/item/circlegame/proc/GOTTEM(mob/living/owner, mob/living/sucker)
+ if(QDELETED(sucker))
+ return
+
+ if(QDELETED(src) || QDELETED(owner))
+ to_chat(sucker, "Nevermind... must've been your imagination...")
+ return
+
+ if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE))
+ to_chat(sucker, "Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]...")
+ return
+
+ to_chat(owner, "[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!")
+ to_chat(sucker, "[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!")
+
+ playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1)
+ owner.do_attack_animation(sucker)
+
+ if(HAS_TRAIT(owner, TRAIT_HULK))
+ owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!", \
+ "You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!", "You hear a sickening sound of flesh hitting flesh!", ignored_mobs=list(sucker))
+ to_chat(sucker, "[owner] bops you incredibly hard with [owner.p_their()] [src.name], sending you flying!")
+ sucker.apply_damage(50, STAMINA)
+ sucker.Knockdown(50)
+ log_combat(owner, sucker, "bopped", src.name, "(setup- Hulk)")
+ var/atom/throw_target = get_edge_target_turf(sucker, owner.dir)
+ sucker.throw_at(throw_target, 6, 3, owner)
+ else
+ owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name]!", "You bop [sucker] with your [src.name]!", \
+ "You hear a dull thud!", ignored_mobs=list(sucker))
+ sucker.apply_damage(15, STAMINA)
+ log_combat(owner, sucker, "bopped", src.name, "(setup)")
+ to_chat(sucker, "[owner] bops you with [owner.p_their()] [src.name]!")
+ qdel(src)
+
/obj/item/slapper
name = "slapper"
desc = "This is how real men fight."
@@ -772,7 +1030,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
block_chance = 40
throwforce = 20
throw_speed = 4
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
attack_verb = list("cut", "sliced", "diced")
w_class = WEIGHT_CLASS_BULKY
slot_flags = ITEM_SLOT_BACK
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 1203c5a1df..14017ffb2d 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -105,15 +105,19 @@
/obj/proc/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0) //used by attack_alien, attack_animal, and attack_slime
if(SEND_SIGNAL(src, COMSIG_OBJ_ATTACK_GENERIC, user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration) & COMPONENT_STOP_GENERIC_ATTACK)
return FALSE
+ if(!user.CheckActionCooldown(CLICK_CD_MELEE))
+ return
user.do_attack_animation(src)
- user.changeNext_move(CLICK_CD_MELEE)
- return take_damage(damage_amount, damage_type, damage_flag, sound_effect, get_dir(src, user), armor_penetration)
+ . = take_damage(damage_amount, damage_type, damage_flag, sound_effect, get_dir(src, user), armor_penetration)
+ user.DelayNextAction()
/obj/attack_alien(mob/living/carbon/alien/humanoid/user)
if(attack_generic(user, 60, BRUTE, "melee", 0))
playsound(src.loc, 'sound/weapons/slash.ogg', 100, 1)
/obj/attack_animal(mob/living/simple_animal/M)
+ if(!M.CheckActionCooldown(CLICK_CD_MELEE))
+ return
if(!M.melee_damage_upper && !M.obj_damage)
M.emote("custom", message = "[M.friendly_verb_continuous] [src].")
return 0
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index ba3eba9bd3..79dd13b0ae 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -10,6 +10,11 @@
var/damtype = BRUTE
var/force = 0
+ /// How good a given object is at causing wounds on carbons. Higher values equal better shots at creating serious wounds.
+ var/wound_bonus = 0
+ /// If this attacks a human with no wound armor on the affected body part, add this to the wound mod. Some attacks may be significantly worse at wounding if there's even a slight layer of armor to absorb some of it vs bare flesh
+ var/bare_wound_bonus = 0
+
var/datum/armor/armor
var/obj_integrity //defaults to max_integrity
var/max_integrity = 500
@@ -37,13 +42,9 @@
if("anchored")
setAnchored(vval)
return TRUE
- if("obj_flags")
+ if(NAMEOF(src, obj_flags))
if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION))
return FALSE
- if("control_object")
- var/obj/O = vval
- if(istype(O) && (O.obj_flags & DANGEROUS_POSSESSION))
- return FALSE
return ..()
/obj/Initialize()
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index d30f617919..b758317fe2 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -9,8 +9,10 @@
var/mob/living/structureclimber
var/broken = 0 //similar to machinery's stat BROKEN
layer = BELOW_OBJ_LAYER
- flags_ricochet = RICOCHET_HARD
- ricochet_chance_mod = 0.5
+ //ricochets on structures commented out for now because there's a lot of structures that /shouldnt/ be ricocheting and those need to be reviewed first
+ //flags_1 = DEFAULT_RICOCHET_1
+ //flags_ricochet = RICOCHET_HARD
+ //ricochet_chance_mod = 0.5
/obj/structure/Initialize()
if (!armor)
@@ -28,12 +30,10 @@
queue_smooth_neighbors(src)
return ..()
-/obj/structure/attack_hand(mob/user)
+/obj/structure/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
- if(.)
- return
if(structureclimber && structureclimber != user)
- user.changeNext_move(CLICK_CD_MELEE)
+ user.DelayNextAction(CLICK_CD_MELEE)
user.do_attack_animation(src)
structureclimber.DefaultCombatKnockdown(40)
structureclimber.visible_message("[structureclimber] has been knocked off [src].", "You're knocked off [src]!", "You see [structureclimber] get knocked off [src].")
diff --git a/code/game/objects/structures/aliens.dm b/code/game/objects/structures/aliens.dm
index a793459c5d..ce11daec54 100644
--- a/code/game/objects/structures/aliens.dm
+++ b/code/game/objects/structures/aliens.dm
@@ -249,7 +249,7 @@
/obj/structure/alien/egg/attack_alien(mob/living/carbon/alien/user)
return attack_hand(user)
-/obj/structure/alien/egg/attack_hand(mob/living/user)
+/obj/structure/alien/egg/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
@@ -269,8 +269,7 @@
return
else
to_chat(user, "It feels slimy.")
- user.changeNext_move(CLICK_CD_MELEE)
-
+ user.DelayNextAction(CLICK_CD_MELEE)
/obj/structure/alien/egg/proc/Grow()
status = GROWN
diff --git a/code/game/objects/structures/artstuff.dm b/code/game/objects/structures/artstuff.dm
index fcc26d1313..47584a1775 100644
--- a/code/game/objects/structures/artstuff.dm
+++ b/code/game/objects/structures/artstuff.dm
@@ -45,8 +45,6 @@
var/height = 11
var/list/grid
var/canvas_color = "#ffffff" //empty canvas color
- var/ui_x = 400
- var/ui_y = 400
var/used = FALSE
var/painting_name //Painting name, this is set after framing.
var/finalized = FALSE //Blocks edits
@@ -75,12 +73,16 @@
. = ..()
ui_interact(user)
-/obj/item/canvas/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+/obj/item/canvas/ui_state(mob/user)
+ if(finalized)
+ return GLOB.physical_obscured_state
+ else
+ return GLOB.default_state
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/item/canvas/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "canvas", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "Canvas", name)
ui.set_autoupdate(FALSE)
ui.open()
@@ -190,8 +192,6 @@
icon_state = "19x19"
width = 19
height = 19
- ui_x = 600
- ui_y = 600
pixel_x = 6
pixel_y = 9
framed_offset_x = 8
@@ -201,8 +201,6 @@
icon_state = "23x19"
width = 23
height = 19
- ui_x = 800
- ui_y = 600
pixel_x = 4
pixel_y = 10
framed_offset_x = 6
@@ -212,8 +210,6 @@
icon_state = "23x23"
width = 23
height = 23
- ui_x = 800
- ui_y = 800
pixel_x = 5
pixel_y = 9
framed_offset_x = 5
@@ -262,7 +258,7 @@
/obj/structure/sign/painting/examine(mob/user)
. = ..()
if(C)
- C.ui_interact(user,state = GLOB.physical_obscured_state)
+ C.ui_interact(user)
/obj/structure/sign/painting/wirecutter_act(mob/living/user, obj/item/I)
. = ..()
diff --git a/code/game/objects/structures/barsigns.dm b/code/game/objects/structures/barsigns.dm
index 821409c26b..905888c904 100644
--- a/code/game/objects/structures/barsigns.dm
+++ b/code/game/objects/structures/barsigns.dm
@@ -52,10 +52,7 @@
/obj/structure/sign/barsign/attack_ai(mob/user)
return attack_hand(user)
-/obj/structure/sign/barsign/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/sign/barsign/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!allowed(user))
to_chat(user, "Access denied.")
return
diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm
index 299ba7b659..f3c4679d21 100644
--- a/code/game/objects/structures/beds_chairs/chair.dm
+++ b/code/game/objects/structures/beds_chairs/chair.dm
@@ -319,10 +319,43 @@
throwforce = 10
throw_range = 3
hitsound = 'sound/items/trayhit1.ogg'
- block_chance = 50
custom_materials = list(/datum/material/iron = 2000)
var/break_chance = 5 //Likely hood of smashing the chair.
var/obj/structure/chair/origin_type = /obj/structure/chair
+ item_flags = ITEM_CAN_PARRY | ITEM_CAN_BLOCK
+ block_parry_data = /datum/block_parry_data/chair
+
+/obj/item/chair/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, require_twohands = TRUE)
+
+/datum/block_parry_data/chair
+ block_damage_multiplier = 0.7
+ block_stamina_efficiency = 2
+ block_stamina_cost_per_second = 1.5
+ block_slowdown = 0.5
+ block_lock_attacking = FALSE
+ block_lock_sprinting = TRUE
+ block_start_delay = 1.5
+ block_damage_absorption = 7
+ block_damage_limit = 20
+ block_resting_stamina_penalty_multiplier = 2
+ block_projectile_mitigation = 20
+ parry_stamina_cost = 5
+ parry_time_windup = 1
+ parry_time_active = 11
+ parry_time_spindown = 2
+ parry_time_perfect = 1.5
+ parry_time_perfect_leeway = 1
+ parry_imperfect_falloff_percent = 7.5
+ parry_efficiency_to_counterattack = 100
+ parry_efficiency_considered_successful = 50
+ parry_efficiency_perfect = 120
+ parry_efficiency_perfect_override = list(
+ TEXT_ATTACK_TYPE_PROJECTILE = 30,
+ )
+ parry_failed_stagger_duration = 3.5 SECONDS
+ parry_data = list(PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN = 2.5)
/obj/item/chair/suicide_act(mob/living/carbon/user)
user.visible_message("[user] begins hitting [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm
index a4ee2f16e2..45cac650b8 100644
--- a/code/game/objects/structures/bedsheet_bin.dm
+++ b/code/game/objects/structures/bedsheet_bin.dm
@@ -311,10 +311,7 @@ LINEN BINS
/obj/structure/bedsheetbin/attack_paw(mob/user)
return attack_hand(user)
-/obj/structure/bedsheetbin/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/bedsheetbin/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(user.incapacitated())
return
if(amount >= 1)
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 5da04a6686..043232e9e3 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -37,12 +37,13 @@
var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further
var/eigen_teleport = FALSE //If the closet leads to Mr Tumnus.
var/obj/structure/closet/eigen_target //Where you go to.
-
+ var/should_populate_contents = TRUE
/obj/structure/closet/Initialize(mapload)
. = ..()
update_icon()
- PopulateContents()
+ if(should_populate_contents)
+ PopulateContents()
if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
addtimer(CALLBACK(src, .proc/take_contents), 0)
if(secure)
@@ -458,10 +459,7 @@
return
container_resist(user)
-/obj/structure/closet/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/closet/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(user.lying && get_dist(src, user) > 0)
return
@@ -506,8 +504,6 @@
if(opened)
return
if(ismovable(loc))
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
var/atom/movable/AM = loc
AM.relay_container_resist(user, src)
return
@@ -516,8 +512,6 @@
return
//okay, so the closet is either welded or locked... resist!!!
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message("[src] begins to shake violently!", \
"You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \
"You hear banging from [src].")
diff --git a/code/game/objects/structures/crates_lockers/closets/genpop.dm b/code/game/objects/structures/crates_lockers/closets/genpop.dm
index 2b263bb1ed..7be12a4819 100644
--- a/code/game/objects/structures/crates_lockers/closets/genpop.dm
+++ b/code/game/objects/structures/crates_lockers/closets/genpop.dm
@@ -91,7 +91,7 @@
locked = TRUE
return ..()
-/obj/structure/closet/secure_closet/genpop/attack_hand(mob/user)
+/obj/structure/closet/secure_closet/genpop/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(user.lying && get_dist(src, user) > 0)
return
@@ -114,4 +114,4 @@
return
- ..()
\ No newline at end of file
+ ..()
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
index 05798df953..4d11bb0ac1 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
@@ -5,6 +5,9 @@
/obj/structure/closet/secure_closet/engineering_chief/PopulateContents()
..()
+ //SKYRAT EDIT - ATMS
+ new /obj/item/pipe_dispenser/bluespace(src) // SKYRAT: CE gets BPRPD instead of RPD, since they start with all other power tools too
+ //SKYRAT EDIT END
new /obj/item/clothing/neck/cloak/ce(src)
new /obj/item/clothing/head/beret/ce(src)
new /obj/item/clothing/under/rank/engineering/chief_engineer(src)
@@ -21,7 +24,6 @@
new /obj/item/assembly/flash/handheld(src)
new /obj/item/clothing/glasses/meson/engine(src)
new /obj/item/door_remote/chief_engineer(src)
- new /obj/item/pipe_dispenser(src)
new /obj/item/inducer(src)
new /obj/item/circuitboard/machine/techfab/department/engineering(src)
new /obj/item/extinguisher/advanced(src)
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 2c977a34d6..ee202f9504 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -41,10 +41,8 @@
if(manifest)
. += "manifest"
-/obj/structure/closet/crate/attack_hand(mob/user)
+/obj/structure/closet/crate/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
- if(.)
- return
if(manifest)
tear_manifest(user)
@@ -144,6 +142,9 @@
desc = "A freezer containing packs of blood."
icon_state = "surgery"
+/obj/structure/closet/crate/freezer/blood/fake
+ should_populate_contents = FALSE
+
/obj/structure/closet/crate/freezer/blood/PopulateContents()
. = ..()
new /obj/item/reagent_containers/blood(src)
@@ -164,6 +165,9 @@
name = "surplus prosthetic limbs"
desc = "A crate containing an assortment of cheap prosthetic limbs."
+/obj/structure/closet/crate/freezer/surplus_limbs/fake
+ should_populate_contents = FALSE
+
/obj/structure/closet/crate/freezer/surplus_limbs/PopulateContents()
. = ..()
new /obj/item/bodypart/l_arm/robot/surplus(src)
@@ -198,6 +202,9 @@
name = "\improper RCD crate"
icon_state = "engi_crate"
+/obj/structure/closet/crate/rcd/fake
+ should_populate_contents = FALSE
+
/obj/structure/closet/crate/rcd/PopulateContents()
..()
for(var/i in 1 to 4)
diff --git a/code/game/objects/structures/crates_lockers/crates/large.dm b/code/game/objects/structures/crates_lockers/crates/large.dm
index 880460a23c..3cee96e435 100644
--- a/code/game/objects/structures/crates_lockers/crates/large.dm
+++ b/code/game/objects/structures/crates_lockers/crates/large.dm
@@ -8,7 +8,7 @@
delivery_icon = "deliverybox"
integrity_failure = 0 //Makes the crate break when integrity reaches 0, instead of opening and becoming an invisible sprite.
-/obj/structure/closet/crate/large/attack_hand(mob/user)
+/obj/structure/closet/crate/large/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
add_fingerprint(user)
if(manifest)
tear_manifest(user)
@@ -40,4 +40,4 @@
else
to_chat(user, "You need a crowbar to pry this open!")
return FALSE //Just stop. Do nothing. Don't turn into an invisible sprite. Don't open like a locker.
- //The large crate has no non-attack interactions other than the crowbar, anyway.
\ No newline at end of file
+ //The large crate has no non-attack interactions other than the crowbar, anyway.
diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm
index c332a07edf..872c74d590 100644
--- a/code/game/objects/structures/displaycase.dm
+++ b/code/game/objects/structures/displaycase.dm
@@ -9,6 +9,8 @@
armor = list("melee" = 30, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100)
max_integrity = 200
integrity_failure = 0.25
+ attack_hand_speed = CLICK_CD_MELEE
+ attack_hand_is_action = TRUE
var/obj/item/showpiece = null
var/alert = TRUE
var/open = FALSE
@@ -157,11 +159,7 @@
/obj/structure/displaycase/attack_paw(mob/user)
return attack_hand(user)
-/obj/structure/displaycase/attack_hand(mob/user)
- . = ..()
- if(.)
- return
- user.changeNext_move(CLICK_CD_MELEE)
+/obj/structure/displaycase/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if (showpiece && (broken || open))
to_chat(user, "You deactivate the hover field built into the case.")
log_combat(user, src, "deactivates the hover field of")
@@ -224,12 +222,12 @@
else
return ..()
-//The captains display case requiring specops ID access is intentional.
+//The captains display case requiring specops ID access is intentional. "that was a joke, totally useless, everyone broke it anyways, atleast the captain can have his OWN THING -- sandstorm edit"
//The lab cage and captains display case do not spawn with electronics, which is why req_access is needed.
/obj/structure/displaycase/captain
alert = TRUE
start_showpiece_type = /obj/item/gun/energy/laser/captain
- req_access = list(ACCESS_CENT_SPECOPS)
+ req_access = list(ACCESS_CAPTAIN)
/obj/structure/displaycase/labcage
name = "lab cage"
@@ -347,3 +345,212 @@
name = initial(I.name)
icon = initial(I.icon)
icon_state = initial(I.icon_state)
+/* Selling people in jars is currently disabled.
+/obj/structure/displaycase/forsale
+ name = "vend-a-tray"
+ icon = 'icons/obj/stationobjs.dmi'
+ icon_state = "laserbox0"
+ desc = "A display case with an ID-card swiper. Use your ID to purchase the contents."
+ density = FALSE
+ max_integrity = 100
+ req_access = null
+ showpiece_type = /obj/item/reagent_containers/food
+ alert = FALSE //No, we're not calling the fire department because someone stole your cookie.
+ glass_fix = FALSE //Fixable with tools instead.
+ ///The price of the item being sold. Altered by grab intent ID use.
+ var/sale_price = 20
+ ///The Account which will receive payment for purchases. Set by the first ID to swipe the tray.
+ var/datum/bank_account/payments_acc = null
+ ///We're using the same trick as paper does in order to cache the image, and only load the UI when messed with.
+ var/list/viewing_ui = list()
+
+/obj/structure/displaycase/forsale/update_icon() //remind me to fix my shitcode later
+ var/icon/I
+ if(open)
+ I = icon('icons/obj/stationobjs.dmi',"laserboxb0")
+ else
+ I = icon('icons/obj/stationobjs.dmi',"laserbox0")
+ if(!showpiece && !open)
+ I = icon('icons/obj/stationobjs.dmi',"laserbox_open")
+ if(broken)
+ I = icon('icons/obj/stationobjs.dmi',"laserbox_broken")
+ if(showpiece)
+ var/icon/S = getFlatIcon(showpiece)
+ S.Scale(17,17)
+ I.Blend(S,ICON_UNDERLAY,8,12)
+ src.icon = I
+ return
+
+/obj/structure/displaycase/forsale/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Vendatray", name)
+ ui.set_autoupdate(FALSE)
+ viewing_ui[user] = ui
+ ui.open()
+
+/obj/structure/displaycase/forsale/ui_data(mob/user)
+ var/list/data = list()
+ var/register = FALSE
+ if(payments_acc)
+ register = TRUE
+ data["owner_name"] = payments_acc.account_holder
+ if(showpiece)
+ data["product_name"] = capitalize(showpiece.name)
+ var/base64 = icon2base64(icon(showpiece.icon, showpiece.icon_state))
+ data["product_icon"] = base64
+ data["registered"] = register
+ data["product_cost"] = sale_price
+ data["tray_open"] = open
+ return data
+
+/obj/structure/displaycase/forsale/ui_act(action, params)
+ if(..())
+ return
+ var/obj/item/card/id/potential_acc = usr.get_idcard(hand_first = TRUE)
+ switch(action)
+ if("Buy")
+ if(!showpiece)
+ to_chat(usr, "There's nothing for sale.")
+ return TRUE
+ if(broken)
+ to_chat(usr, "[src] appears to be broken.")
+ return TRUE
+ if(!payments_acc)
+ to_chat(usr, "[src] hasn't been registered yet.")
+ return TRUE
+ if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return TRUE
+ if(!potential_acc)
+ to_chat(usr, "No ID card detected.")
+ return
+ var/datum/bank_account/account = potential_acc.registered_account
+ if(!account)
+ to_chat(usr, "[potential_acc] has no account registered!")
+ return
+ if(!account.has_money(sale_price))
+ to_chat(usr, "You do not possess the funds to purchase this.")
+ return TRUE
+ else
+ account.adjust_money(-sale_price)
+ if(payments_acc)
+ payments_acc.adjust_money(sale_price)
+ usr.put_in_hands(showpiece)
+ to_chat(usr, "You purchase [showpiece] for [sale_price] credits.")
+ playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE)
+ icon = 'icons/obj/stationobjs.dmi'
+ flick("laserbox_vend", src)
+ showpiece = null
+ update_icon()
+ SStgui.update_uis(src)
+ return TRUE
+ if("Open")
+ if(!payments_acc)
+ to_chat(usr, "[src] hasn't been registered yet.")
+ return TRUE
+ if(!potential_acc || !potential_acc.registered_account)
+ return
+ if(!check_access(potential_acc))
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
+ return
+ toggle_lock()
+ SStgui.update_uis(src)
+ if("Register")
+ if(payments_acc)
+ return
+ if(!potential_acc || !potential_acc.registered_account)
+ return
+ if(!check_access(potential_acc))
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
+ return
+ payments_acc = potential_acc.registered_account
+ playsound(src, 'sound/machines/click.ogg', 20, TRUE)
+ if("Adjust")
+ if(!check_access(potential_acc) || potential_acc.registered_account != payments_acc)
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
+ return
+
+ var/new_price_input = input(usr,"Set the sale price for this vend-a-tray.","new price",0) as num|null
+ if(isnull(new_price_input) || (payments_acc != potential_acc.registered_account))
+ to_chat(usr, "[src] rejects your new price.")
+ return
+ if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) )
+ to_chat(usr, "You need to get closer!")
+ return
+ new_price_input = clamp(round(new_price_input, 1), 10, 1000)
+ sale_price = new_price_input
+ to_chat(usr, "The cost is now set to [sale_price].")
+ SStgui.update_uis(src)
+ return TRUE
+ . = TRUE
+/obj/structure/displaycase/forsale/attackby(obj/item/I, mob/living/user, params)
+ if(isidcard(I))
+ //Card Registration
+ var/obj/item/card/id/potential_acc = I
+ if(!potential_acc.registered_account)
+ to_chat(user, "This ID card has no account registered!")
+ return
+ if(payments_acc == potential_acc.registered_account)
+ playsound(src, 'sound/machines/click.ogg', 20, TRUE)
+ toggle_lock()
+ return
+ if(istype(I, /obj/item/pda))
+ return TRUE
+ SStgui.update_uis(src)
+ . = ..()
+
+
+/obj/structure/displaycase/forsale/multitool_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(obj_integrity <= (integrity_failure * max_integrity))
+ to_chat(user, "You start recalibrating [src]'s hover field...")
+ if(do_after(user, 20, target = src))
+ broken = 0
+ obj_integrity = max_integrity
+ update_icon()
+ return TRUE
+
+/obj/structure/displaycase/forsale/wrench_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(open && user.a_intent == INTENT_HELP )
+ if(anchored)
+ to_chat(user, "You start unsecuring [src]...")
+ else
+ to_chat(user, "You start securing [src]...")
+ if(I.use_tool(src, user, 16, volume=50))
+ if(QDELETED(I))
+ return
+ if(anchored)
+ to_chat(user, "You unsecure [src].")
+ else
+ to_chat(user, "You secure [src].")
+ anchored = !anchored
+ return
+ else if(!open && user.a_intent == INTENT_HELP)
+ to_chat(user, "[src] must be open to move it.")
+ return
+
+/obj/structure/displaycase/forsale/emag_act(mob/user)
+ . = ..()
+ payments_acc = null
+ req_access = list()
+ to_chat(user, "[src]'s card reader fizzles and smokes, and the account owner is reset.")
+
+/obj/structure/displaycase/forsale/examine(mob/user)
+ . = ..()
+ if(showpiece && !open)
+ . += "[showpiece] is for sale for [sale_price] credits."
+ if(broken)
+ . += "[src] is sparking and the hover field generator seems to be overloaded. Use a multitool to fix it."
+
+/obj/structure/displaycase/forsale/obj_break(damage_flag)
+ if(!broken && !(flags_1 & NODECONSTRUCT_1))
+ broken = TRUE
+ playsound(src, "shatter", 70, TRUE)
+ update_icon()
+ trigger_alarm() //In case it's given an alarm anyway.
+
+/obj/structure/displaycase/forsale/kitchen
+ desc = "A display case with an ID-card swiper. Use your ID to purchase the contents. Meant for the bartender and chef."
+ req_one_access = list(ACCESS_KITCHEN, ACCESS_BAR)
+*/
\ No newline at end of file
diff --git a/code/game/objects/structures/divine.dm b/code/game/objects/structures/divine.dm
index bca96e67d1..f64397df09 100644
--- a/code/game/objects/structures/divine.dm
+++ b/code/game/objects/structures/divine.dm
@@ -7,7 +7,7 @@
density = FALSE
can_buckle = 1
-/obj/structure/sacrificealtar/attack_hand(mob/living/user)
+/obj/structure/sacrificealtar/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
@@ -30,7 +30,7 @@
var/time_between_uses = 1800
var/last_process = 0
-/obj/structure/healingfountain/attack_hand(mob/living/user)
+/obj/structure/healingfountain/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
@@ -48,4 +48,4 @@
if(last_process + time_between_uses > world.time)
icon_state = "fountain"
else
- icon_state = "fountain-red"
\ No newline at end of file
+ icon_state = "fountain-red"
diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm
index 528153324d..7fdec9b3d9 100644
--- a/code/game/objects/structures/dresser.dm
+++ b/code/game/objects/structures/dresser.dm
@@ -19,7 +19,7 @@
new /obj/item/stack/sheet/mineral/wood(drop_location(), 10)
qdel(src)
-/obj/structure/dresser/attack_hand(mob/user)
+/obj/structure/dresser/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(. || !ishuman(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
@@ -46,7 +46,7 @@
switch(choice)
if("Underwear")
var/new_undies = input(H, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list
- if(H.underwear)
+ if(new_undies)
H.underwear = new_undies
H.saved_underwear = new_undies
var/datum/sprite_accessory/underwear/bottom/B = GLOB.underwear_list[new_undies]
diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm
index 3bc84deb4d..84926ccfb0 100644
--- a/code/game/objects/structures/extinguisher.dm
+++ b/code/game/objects/structures/extinguisher.dm
@@ -69,10 +69,7 @@
return ..()
-/obj/structure/extinguisher_cabinet/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/extinguisher_cabinet/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(iscyborg(user) || isalien(user))
return
if(stored_extinguisher)
diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm
index 679a755321..b654f4f5c0 100644
--- a/code/game/objects/structures/false_walls.dm
+++ b/code/game/objects/structures/false_walls.dm
@@ -41,7 +41,7 @@
new /obj/structure/falsewall/brass(loc)
qdel(src)
-/obj/structure/falsewall/attack_hand(mob/user)
+/obj/structure/falsewall/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(opening)
return
. = ..()
@@ -180,7 +180,7 @@
radiate()
return ..()
-/obj/structure/falsewall/uranium/attack_hand(mob/user)
+/obj/structure/falsewall/uranium/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
radiate()
. = ..()
diff --git a/code/game/objects/structures/femur_breaker.dm b/code/game/objects/structures/femur_breaker.dm
index 7331285161..2ac56ec4fb 100644
--- a/code/game/objects/structures/femur_breaker.dm
+++ b/code/game/objects/structures/femur_breaker.dm
@@ -32,7 +32,7 @@
if (LAZYLEN(buckled_mobs))
. += "Someone appears to be strapped in. You can help them unbuckle, or activate the femur breaker."
-/obj/structure/femur_breaker/attack_hand(mob/user)
+/obj/structure/femur_breaker/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
add_fingerprint(user)
// Currently being used
diff --git a/code/game/objects/structures/fence.dm b/code/game/objects/structures/fence.dm
index 35891fa607..f90161f587 100644
--- a/code/game/objects/structures/fence.dm
+++ b/code/game/objects/structures/fence.dm
@@ -120,7 +120,7 @@
open = TRUE
density = TRUE
-/obj/structure/fence/door/attack_hand(mob/user)
+/obj/structure/fence/door/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(can_open(user))
toggle(user)
@@ -156,4 +156,4 @@
#undef NO_HOLE
#undef MEDIUM_HOLE
#undef LARGE_HOLE
-#undef MAX_HOLE_SIZE
\ No newline at end of file
+#undef MAX_HOLE_SIZE
diff --git a/code/game/objects/structures/fireaxe.dm b/code/game/objects/structures/fireaxe.dm
index bcf1016c1e..412730910f 100644
--- a/code/game/objects/structures/fireaxe.dm
+++ b/code/game/objects/structures/fireaxe.dm
@@ -105,10 +105,7 @@
fireaxe = null
qdel(src)
-/obj/structure/fireaxecabinet/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/fireaxecabinet/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(open || broken)
if(fireaxe)
user.put_in_hands(fireaxe)
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index ce78b92f38..b587e57c3a 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -65,7 +65,7 @@
var/gift_type = /obj/item/a_gift/anything
var/list/ckeys_that_took = list()
-/obj/structure/flora/tree/pine/xmas/presents/attack_hand(mob/living/user)
+/obj/structure/flora/tree/pine/xmas/presents/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
diff --git a/code/game/objects/structures/fluff.dm b/code/game/objects/structures/fluff.dm
index 736e58143e..bd7ddb36d5 100644
--- a/code/game/objects/structures/fluff.dm
+++ b/code/game/objects/structures/fluff.dm
@@ -110,7 +110,7 @@
desc = "Space Jesus is my copilot."
icon_state = "driverseat"
-/obj/structure/fluff/bus/passable/seat/driver/attack_hand(mob/user)
+/obj/structure/fluff/bus/passable/seat/driver/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
playsound(src, 'sound/items/carhorn.ogg', 50, 1)
. = ..()
diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm
index 4455c8820d..4d500837d9 100644
--- a/code/game/objects/structures/ghost_role_spawners.dm
+++ b/code/game/objects/structures/ghost_role_spawners.dm
@@ -186,10 +186,7 @@
else
new_spawn.mind.assigned_role = "Free Golem"
-/obj/effect/mob_spawn/human/golem/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/effect/mob_spawn/human/golem/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(isgolem(user) && can_transfer)
var/transfer_choice = alert("Transfer your soul to [src]? (Warning, your old body will die!)",,"Yes","No")
if(transfer_choice != "Yes" || QDELETED(src) || uses <= 0 || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERY, NO_TK))
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 20151a0bdb..36e4f825da 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -10,6 +10,8 @@
layer = BELOW_OBJ_LAYER
armor = list("melee" = 50, "bullet" = 70, "laser" = 70, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
max_integrity = 50
+ attack_hand_is_action = TRUE
+ attack_hand_speed = 8
integrity_failure = 0.4
var/rods_type = /obj/item/stack/rods
var/rods_amount = 2
@@ -99,11 +101,10 @@
..(user, 1)
return TRUE
-/obj/structure/grille/attack_hand(mob/living/user)
+/obj/structure/grille/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
- user.changeNext_move(CLICK_CD_MELEE)
user.do_attack_animation(src, ATTACK_EFFECT_KICK)
user.visible_message("[user] hits [src].", null, null, COMBAT_MESSAGE_RANGE)
log_combat(user, src, "hit")
@@ -111,8 +112,9 @@
take_damage(rand(5,10), BRUTE, "melee", 1)
/obj/structure/grille/attack_alien(mob/living/user)
+ if(!user.CheckActionCooldown(CLICK_CD_MELEE))
+ return
user.do_attack_animation(src)
- user.changeNext_move(CLICK_CD_MELEE)
user.visible_message("[user] mangles [src].", null, null, COMBAT_MESSAGE_RANGE)
if(!shock(user, 70))
take_damage(20, BRUTE, "melee", 1)
@@ -134,7 +136,7 @@
. = . || (mover.pass_flags & PASSGRILLE)
/obj/structure/grille/attackby(obj/item/W, mob/user, params)
- user.changeNext_move(CLICK_CD_MELEE)
+ user.DelayNextAction(CLICK_CD_MELEE)
add_fingerprint(user)
if(istype(W, /obj/item/wirecutters))
if(!shock(user, 100))
diff --git a/code/game/objects/structures/guillotine.dm b/code/game/objects/structures/guillotine.dm
index 0a5ba6a68c..4773c07657 100644
--- a/code/game/objects/structures/guillotine.dm
+++ b/code/game/objects/structures/guillotine.dm
@@ -51,7 +51,7 @@
if (LAZYLEN(buckled_mobs))
. += "Someone appears to be strapped in. You can help them out, or you can harm them by activating the guillotine."
-/obj/structure/guillotine/attack_hand(mob/user)
+/obj/structure/guillotine/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
add_fingerprint(user)
// Currently being used by something
@@ -108,7 +108,7 @@
if (QDELETED(head))
return
- playsound(src, 'sound/weapons/bladeslice.ogg', 100, 1)
+ playsound(src, 'sound/weapons/guillotine.ogg', 100, TRUE)
if (blade_sharpness >= GUILLOTINE_DECAP_MIN_SHARP || head.brute_dam >= 100)
head.dismember()
log_combat(user, H, "beheaded", src)
@@ -256,4 +256,4 @@
#undef GUILLOTINE_ACTIVATE_DELAY
#undef GUILLOTINE_WRENCH_DELAY
#undef GUILLOTINE_ACTION_INUSE
-#undef GUILLOTINE_ACTION_WRENCH
\ No newline at end of file
+#undef GUILLOTINE_ACTION_WRENCH
diff --git a/code/game/objects/structures/guncase.dm b/code/game/objects/structures/guncase.dm
index ede7e31e0d..e7ff9d4abf 100644
--- a/code/game/objects/structures/guncase.dm
+++ b/code/game/objects/structures/guncase.dm
@@ -53,10 +53,7 @@
else
return ..()
-/obj/structure/guncase/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/guncase/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(iscyborg(user) || isalien(user))
return
if(contents.len && open)
diff --git a/code/game/objects/structures/headpike.dm b/code/game/objects/structures/headpike.dm
index 65d930e08b..6aed11701d 100644
--- a/code/game/objects/structures/headpike.dm
+++ b/code/game/objects/structures/headpike.dm
@@ -37,14 +37,11 @@
MA.pixel_y = 12
. += H
-/obj/structure/headpike/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/headpike/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
to_chat(user, "You take down [src].")
if(victim)
victim.forceMove(drop_location())
victim = null
spear.forceMove(drop_location())
spear = null
- qdel(src)
\ No newline at end of file
+ qdel(src)
diff --git a/code/game/objects/structures/holosign.dm b/code/game/objects/structures/holosign.dm
index c49159a992..dc5355e9d8 100644
--- a/code/game/objects/structures/holosign.dm
+++ b/code/game/objects/structures/holosign.dm
@@ -25,12 +25,12 @@
projector = null
return ..()
-/obj/structure/holosign/attack_hand(mob/living/user)
+/obj/structure/holosign/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
user.do_attack_animation(src, ATTACK_EFFECT_PUNCH)
- user.changeNext_move(CLICK_CD_MELEE)
+ user.DelayNextAction(CLICK_CD_MELEE)
take_damage(5 , BRUTE, "melee", 1)
/obj/structure/holosign/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
@@ -162,7 +162,7 @@
return TRUE //nice or benign diseases!
return TRUE
-/obj/structure/holosign/barrier/medical/attack_hand(mob/living/user)
+/obj/structure/holosign/barrier/medical/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
if(CanPass(user) && user.a_intent == INTENT_HELP)
force_allaccess = !force_allaccess
to_chat(user, "You [force_allaccess ? "deactivate" : "activate"] the biometric scanners.") //warning spans because you can make the station sick!
@@ -182,7 +182,7 @@
/obj/structure/holosign/barrier/cyborg/hacked/proc/cooldown()
shockcd = FALSE
-/obj/structure/holosign/barrier/cyborg/hacked/attack_hand(mob/living/user)
+/obj/structure/holosign/barrier/cyborg/hacked/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm
index dc4a741b8b..10a9f2afd4 100644
--- a/code/game/objects/structures/janicart.dm
+++ b/code/game/objects/structures/janicart.dm
@@ -91,10 +91,7 @@
else
return ..()
-/obj/structure/janitorialcart/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/janitorialcart/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
var/list/items = list()
if(mybag)
diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm
index 05a7e1c958..a23dd3a21e 100644
--- a/code/game/objects/structures/kitchen_spike.dm
+++ b/code/game/objects/structures/kitchen_spike.dm
@@ -60,8 +60,7 @@
deconstruct(TRUE)
return TRUE
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/structure/kitchenspike/attack_hand(mob/user)
+/obj/structure/kitchenspike/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(VIABLE_MOB_CHECK(user.pulling) && user.a_intent == INTENT_GRAB && !has_buckled_mobs())
var/mob/living/L = user.pulling
if(HAS_TRAIT(user, TRAIT_PACIFISM) && L.stat != DEAD)
diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm
index f321498bfd..e844461952 100644
--- a/code/game/objects/structures/ladders.dm
+++ b/code/game/objects/structures/ladders.dm
@@ -122,10 +122,7 @@
return FALSE
return TRUE
-/obj/structure/ladder/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/ladder/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
use(user)
/obj/structure/ladder/attack_paw(mob/user)
diff --git a/code/game/objects/structures/life_candle.dm b/code/game/objects/structures/life_candle.dm
index a64dc6c6a9..52986a44d7 100644
--- a/code/game/objects/structures/life_candle.dm
+++ b/code/game/objects/structures/life_candle.dm
@@ -24,10 +24,7 @@
var/respawn_time = 50
var/respawn_sound = 'sound/magic/staff_animation.ogg'
-/obj/structure/life_candle/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/life_candle/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!user.mind)
return
if(user.mind in linked_minds)
@@ -38,7 +35,7 @@
linked_minds |= user.mind
update_icon()
- float(linked_minds.len)
+ INVOKE_ASYNC(src, /atom/movable.proc/float, linked_minds.len)
if(linked_minds.len)
START_PROCESSING(SSobj, src)
set_light(lit_luminosity)
diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm
index 79489e4ae6..e8fbafa42b 100644
--- a/code/game/objects/structures/manned_turret.dm
+++ b/code/game/objects/structures/manned_turret.dm
@@ -198,10 +198,6 @@
/obj/item/gun_control/CanItemAutoclick()
return TRUE
-/obj/item/gun_control/attack_obj(obj/O, mob/living/user)
- user.changeNext_move(CLICK_CD_MELEE)
- O.attacked_by(src, user)
-
/obj/item/gun_control/attack(mob/living/M, mob/living/user)
M.lastattacker = user.real_name
M.lastattackerckey = user.ckey
diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm
index f52fa0576a..b2f1de8750 100644
--- a/code/game/objects/structures/mineral_doors.dm
+++ b/code/game/objects/structures/mineral_doors.dm
@@ -50,10 +50,7 @@
/obj/structure/mineral_door/attack_paw(mob/user)
return attack_hand(user)
-/obj/structure/mineral_door/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/mineral_door/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
return TryToSwitchState(user)
/obj/structure/mineral_door/CanPass(atom/movable/mover, turf/target)
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index 65e1b7dd9a..237cfdbf0b 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -15,10 +15,7 @@
if(icon_state == "mirror_broke" && !broken)
obj_break(null, mapload)
-/obj/structure/mirror/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/mirror/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(broken || !Adjacent(user))
return
@@ -118,10 +115,7 @@
choosable_races += S.id
..()
-/obj/structure/mirror/magic/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/mirror/magic/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!ishuman(user))
return
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 79a7ce0519..82ca59161c 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -22,7 +22,8 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
anchored = TRUE
max_integrity = 400
- var/obj/structure/tray/connected = null
+ var/obj/structure/tray/connected
+ var/starting_tray
var/locked = FALSE
dir = SOUTH
var/message_cooldown
@@ -30,6 +31,9 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
/obj/structure/bodycontainer/Initialize()
. = ..()
+ if(starting_tray)
+ connected = new starting_tray(src)
+ connected.connected = src
GLOB.bodycontainers += src
recursive_organ_check(src)
@@ -37,8 +41,7 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
GLOB.bodycontainers -= src
open()
if(connected)
- qdel(connected)
- connected = null
+ QDEL_NULL(connected)
return ..()
/obj/structure/bodycontainer/on_log(login)
@@ -58,10 +61,7 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
/obj/structure/bodycontainer/attack_paw(mob/user)
return attack_hand(user)
-/obj/structure/bodycontainer/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/bodycontainer/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(locked)
to_chat(user, "It's locked.")
return
@@ -106,8 +106,6 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
if(!locked)
open()
return
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message(null, \
"You lean on the back of [src] and start pushing the tray open... (this will take about [DisplayTimeText(breakout_time)].)", \
"You hear a metallic creaking from [src].")
@@ -150,15 +148,11 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
desc = "Used to keep bodies in until someone fetches them. Now includes a high-tech alert system."
icon_state = "morgue1"
dir = EAST
+ starting_tray = /obj/structure/tray/m_tray
var/beeper = TRUE
var/beep_cooldown = 50
var/next_beep = 0
-/obj/structure/bodycontainer/morgue/New()
- connected = new/obj/structure/tray/m_tray(src)
- connected.connected = src
- ..()
-
/obj/structure/bodycontainer/morgue/examine(mob/user)
. = ..()
. += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it."
@@ -208,6 +202,7 @@ GLOBAL_LIST_EMPTY(crematoriums)
desc = "A human incinerator. Works well on barbecue nights."
icon_state = "crema1"
dir = SOUTH
+ starting_tray = /obj/structure/tray/c_tray
var/id = 1
/obj/structure/bodycontainer/crematorium/attack_robot(mob/user) //Borgs can't use crematoriums without help
@@ -218,14 +213,14 @@ GLOBAL_LIST_EMPTY(crematoriums)
GLOB.crematoriums.Remove(src)
return ..()
-/obj/structure/bodycontainer/crematorium/New()
- connected = new/obj/structure/tray/c_tray(src)
- connected.connected = src
-
+/obj/structure/bodycontainer/crematorium/Initialize()
+ . = ..()
GLOB.crematoriums.Add(src)
- ..()
-/obj/structure/bodycontainer/crematorium/update_icon_state()
+/obj/structure/bodycontainer/crematorium/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+ id = "[idnum][id]"
+
+/obj/structure/bodycontainer/crematorium/update_icon()
if(!connected || connected.loc != src)
icon_state = "crema0"
else
@@ -322,10 +317,7 @@ GLOBAL_LIST_EMPTY(crematoriums)
/obj/structure/tray/attack_paw(mob/user)
return attack_hand(user)
-/obj/structure/tray/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/tray/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if (src.connected)
connected.close()
add_fingerprint(user)
diff --git a/code/game/objects/structures/spirit_board.dm b/code/game/objects/structures/spirit_board.dm
index 4a9a1bdce6..1b3093e4e7 100644
--- a/code/game/objects/structures/spirit_board.dm
+++ b/code/game/objects/structures/spirit_board.dm
@@ -14,10 +14,7 @@
desc = "[initial(desc)] The planchette is sitting at \"[planchette]\"."
return ..()
-/obj/structure/spirit_board/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/spirit_board/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
spirit_board_pick_letter(user)
@@ -76,4 +73,4 @@
to_chat(M, "There aren't enough people to use the [src.name]!")
return 0
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm
index 466912f93e..f8b54d04b6 100644
--- a/code/game/objects/structures/statues.dm
+++ b/code/game/objects/structures/statues.dm
@@ -74,7 +74,7 @@
radiate()
..()
-/obj/structure/statue/uranium/attack_hand(mob/user)
+/obj/structure/statue/uranium/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
radiate()
. = ..()
@@ -240,7 +240,7 @@
honk()
return ..()
-/obj/structure/statue/bananium/attack_hand(mob/user)
+/obj/structure/statue/bananium/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
honk()
. = ..()
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 777be608b5..a553e63b0f 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -23,6 +23,8 @@
climbable = TRUE
obj_flags = CAN_BE_HIT|SHOVABLE_ONTO
pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.")
+ attack_hand_speed = CLICK_CD_MELEE
+ attack_hand_is_action = TRUE
var/frame = /obj/structure/table_frame
var/framestack = /obj/item/stack/rods
var/buildstack = /obj/item/stack/sheet/metal
@@ -60,7 +62,7 @@
/obj/structure/table/attack_paw(mob/user)
return attack_hand(user)
-/obj/structure/table/attack_hand(mob/living/user)
+/obj/structure/table/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
if(Adjacent(user) && user.pulling)
if(isliving(user.pulling))
var/mob/living/pushed_mob = user.pulling
@@ -71,7 +73,10 @@
if(user.grab_state < GRAB_AGGRESSIVE)
to_chat(user, "You need a better grip to do that!")
return
- tablepush(user, pushed_mob)
+ if(user.grab_state >= GRAB_NECK)
+ tablelimbsmash(user, pushed_mob)
+ else
+ tablepush(user, pushed_mob)
if(user.a_intent == INTENT_HELP)
pushed_mob.visible_message("[user] begins to place [pushed_mob] onto [src]...", \
"[user] begins to place [pushed_mob] onto [src]...")
@@ -138,6 +143,22 @@
H.emote("nya")
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table)
+/obj/structure/table/proc/tablelimbsmash(mob/living/user, mob/living/pushed_mob)
+ pushed_mob.Knockdown(30)
+ var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(user.zone_selected) || pushed_mob.get_bodypart(BODY_ZONE_HEAD)
+ var/extra_wound = 0
+ if(HAS_TRAIT(user, TRAIT_HULK))
+ extra_wound = 20
+ banged_limb.receive_damage(30, wound_bonus = extra_wound)
+ pushed_mob.apply_damage(60, STAMINA)
+ take_damage(50)
+
+ playsound(pushed_mob, 'sound/effects/bang.ogg', 90, TRUE)
+ pushed_mob.visible_message("[user] smashes [pushed_mob]'s [banged_limb.name] against \the [src]!",
+ "[user] smashes your [banged_limb.name] against \the [src]")
+ log_combat(user, pushed_mob, "head slammed", null, "against [src]")
+ SEND_SIGNAL(pushed_mob, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table_limbsmash, banged_limb)
+
/obj/structure/table/shove_act(mob/living/target, mob/living/user)
if(CHECK_MOBILITY(target, MOBILITY_STAND))
target.DefaultCombatKnockdown(SHOVE_KNOCKDOWN_TABLE)
@@ -191,8 +212,10 @@
return
/obj/structure/table/alt_attack_hand(mob/user)
+ if(!user.CheckActionCooldown(CLICK_CD_MELEE))
+ return
+ user.DelayNextAction()
if(user && Adjacent(user) && !user.incapacitated())
- user.changeNext_move(CLICK_CD_MELEE*0.5)
if(istype(user) && user.a_intent == INTENT_HARM)
user.visible_message("[user] slams [user.p_their()] palms down on [src].", "You slam your palms down on [src].")
playsound(src, 'sound/weapons/sonic_jackhammer.ogg', 50, 1)
@@ -553,7 +576,7 @@
change_construction_value(-2)
return ..()
-/obj/structure/table/reinforced/brass/tablepush(mob/living/user, mob/living/pushed_mob)
+/obj/structure/table/reinforced/brass/tablelimbsmash(mob/living/user, mob/living/pushed_mob)
.= ..()
playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE)
@@ -577,7 +600,7 @@
buildstack = /obj/item/stack/tile/bronze
canSmoothWith = list(/obj/structure/table/reinforced/brass, /obj/structure/table/bronze)
-/obj/structure/table/bronze/tablepush(mob/living/user, mob/living/pushed_mob)
+/obj/structure/table/bronze/tablelimbsmash(mob/living/user, mob/living/pushed_mob)
..()
playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE)
@@ -606,7 +629,7 @@
computer.table = src
break
-/obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob)
+/obj/structure/table/optable/tablelimbsmash(mob/living/user, mob/living/pushed_mob)
pushed_mob.forceMove(loc)
pushed_mob.set_resting(TRUE, TRUE)
visible_message("[user] has laid [pushed_mob] on [src].")
@@ -635,6 +658,8 @@
anchored = TRUE
pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.
max_integrity = 20
+ attack_hand_speed = CLICK_CD_MELEE
+ attack_hand_is_action = TRUE
/obj/structure/rack/examine(mob/user)
. = ..()
@@ -676,13 +701,12 @@
/obj/structure/rack/attack_paw(mob/living/user)
attack_hand(user)
-/obj/structure/rack/attack_hand(mob/living/user)
+/obj/structure/rack/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
if(CHECK_MULTIPLE_BITFIELDS(user.mobility_flags, MOBILITY_STAND|MOBILITY_MOVE) || user.get_num_legs() < 2)
return
- user.changeNext_move(CLICK_CD_MELEE)
user.do_attack_animation(src, ATTACK_EFFECT_KICK)
user.visible_message("[user] kicks [src].", null, null, COMBAT_MESSAGE_RANGE)
take_damage(rand(4,8), BRUTE, "melee", 1)
diff --git a/code/game/objects/structures/tank_dispenser.dm b/code/game/objects/structures/tank_dispenser.dm
index 6a8175b921..2a441ff0f2 100644
--- a/code/game/objects/structures/tank_dispenser.dm
+++ b/code/game/objects/structures/tank_dispenser.dm
@@ -67,11 +67,13 @@
to_chat(user, "You put [I] in [src].")
update_icon()
-/obj/structure/tank_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/structure/tank_dispenser/ui_state(mob/user)
+ return GLOB.physical_state
+
+/obj/structure/tank_dispenser/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "tank_dispenser", name, 275, 103, master_ui, state)
+ ui = new(user, src, "TankDispenser", name)
ui.open()
/obj/structure/tank_dispenser/ui_data(mob/user)
diff --git a/code/game/objects/structures/target_stake.dm b/code/game/objects/structures/target_stake.dm
index 9dcfec43bf..c5f3a089f7 100644
--- a/code/game/objects/structures/target_stake.dm
+++ b/code/game/objects/structures/target_stake.dm
@@ -48,10 +48,7 @@
handle_density()
to_chat(user, "You slide the target into the stake.")
-/obj/structure/target_stake/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/target_stake/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(pinned_target)
removeTarget(user)
diff --git a/code/game/objects/structures/transit_tubes/station.dm b/code/game/objects/structures/transit_tubes/station.dm
index ba23878168..3a5eb135f4 100644
--- a/code/game/objects/structures/transit_tubes/station.dm
+++ b/code/game/objects/structures/transit_tubes/station.dm
@@ -58,10 +58,7 @@
qdel(R)
-/obj/structure/transit_tube/station/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/transit_tube/station/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!pod_moving)
if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling))
if(open_status == STATION_TUBE_OPEN)
diff --git a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
index 36539ae1e4..cf68341b2c 100644
--- a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
+++ b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
@@ -69,8 +69,6 @@
empty_pod()
return
if(!moving)
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
to_chat(user, "You start trying to escape from the pod...")
if(do_after(user, 600, target = src))
to_chat(user, "You manage to open the pod.")
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index acb88fbb1d..c52249686a 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -13,6 +13,8 @@
var/mob/living/swirlie = null //the mob being given a swirlie
var/buildstacktype = /obj/item/stack/sheet/metal //they're metal now, shut up
var/buildstackamount = 1
+ attack_hand_speed = CLICK_CD_MELEE
+ attack_hand_is_action = TRUE
/obj/structure/toilet/Initialize()
. = ..()
@@ -26,18 +28,16 @@
AM.forceMove(loc)
return ..()
-/obj/structure/toilet/attack_hand(mob/living/user)
+/obj/structure/toilet/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
if(swirlie)
- user.changeNext_move(CLICK_CD_MELEE)
playsound(src.loc, "swing_hit", 25, 1)
swirlie.visible_message("[user] slams the toilet seat onto [swirlie]'s head!", "[user] slams the toilet seat onto your head!", "You hear reverberating porcelain.")
swirlie.adjustBruteLoss(5)
else if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling))
- user.changeNext_move(CLICK_CD_MELEE)
var/mob/living/GM = user.pulling
if(user.grab_state >= GRAB_AGGRESSIVE)
if(GM.loc != get_turf(src))
@@ -167,6 +167,8 @@
icon_state = "urinal"
density = FALSE
anchored = TRUE
+ attack_hand_speed = CLICK_CD_MELEE
+ attack_hand_is_action = TRUE
var/exposed = 0 // can you currently put an item inside
var/obj/item/hiddenitem = null // what's in the urinal
@@ -174,17 +176,13 @@
..()
hiddenitem = new /obj/item/reagent_containers/food/urinalcake
-/obj/structure/urinal/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/urinal/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling))
var/mob/living/GM = user.pulling
if(user.grab_state >= GRAB_AGGRESSIVE)
if(GM.loc != get_turf(src))
to_chat(user, "[GM.name] needs to be on [src].")
return
- user.changeNext_move(CLICK_CD_MELEE)
user.visible_message("[user] slams [GM] into [src]!", "You slam [GM] into [src]!")
GM.adjustBruteLoss(8)
else
@@ -492,7 +490,7 @@
var/buildstacktype = /obj/item/stack/sheet/metal
var/buildstackamount = 1
-/obj/structure/sink/attack_hand(mob/living/user)
+/obj/structure/sink/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
@@ -707,8 +705,7 @@
icon_state = "puddle"
resistance_flags = UNACIDABLE
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/structure/sink/puddle/attack_hand(mob/M)
+/obj/structure/sink/puddle/on_attack_hand(mob/M)
icon_state = "puddle-splash"
. = ..()
icon_state = "puddle"
@@ -784,10 +781,7 @@
return TRUE
-/obj/structure/curtain/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/curtain/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
playsound(loc, 'sound/effects/curtain.ogg', 50, 1)
toggle()
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 17031a51df..3373278c0c 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -17,7 +17,6 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
layer = ABOVE_OBJ_LAYER //Just above doors
pressure_resistance = 4*ONE_ATMOSPHERE
anchored = TRUE //initially is 0 for tile smoothing
- flags_1 = ON_BORDER_1
max_integrity = 25
var/ini_dir = null
var/state = WINDOW_OUT_OF_FRAME
@@ -38,8 +37,11 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
var/hitsound = 'sound/effects/Glasshit.ogg'
rad_insulation = RAD_VERY_LIGHT_INSULATION
rad_flags = RAD_PROTECT_CONTENTS
- flags_ricochet = RICOCHET_HARD
+ flags_1 = ON_BORDER_1|DEFAULT_RICOCHET_1
+ flags_ricochet = RICOCHET_HARD
ricochet_chance_mod = 0.4
+ attack_hand_speed = CLICK_CD_MELEE
+ attack_hand_is_action = TRUE
/// Electrochromatic status
var/electrochromatic_status = NOT_ELECTROCHROMATIC
@@ -157,7 +159,7 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
return 1
/obj/structure/window/attack_tk(mob/user)
- user.changeNext_move(CLICK_CD_MELEE)
+ user.DelayNextAction(CLICK_CD_MELEE)
user.visible_message("Something knocks on [src].")
add_fingerprint(user)
playsound(src, 'sound/effects/Glassknock.ogg', 50, 1)
@@ -167,18 +169,15 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
return 1
. = ..()
-/obj/structure/window/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/window/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!can_be_reached(user))
return
- user.changeNext_move(CLICK_CD_MELEE)
user.visible_message("[user] knocks on [src].")
add_fingerprint(user)
playsound(src, 'sound/effects/Glassknock.ogg', 50, 1)
/obj/structure/window/attack_paw(mob/user)
+ user.DelayNextAction()
return attack_hand(user)
/obj/structure/window/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) //used by attack_alien, attack_animal, and attack_slime
@@ -836,13 +835,9 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
for (var/i in 1 to rand(1,4))
. += new /obj/item/paper/natural(location)
-/obj/structure/window/paperframe/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/structure/window/paperframe/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
add_fingerprint(user)
if(user.a_intent != INTENT_HARM)
- user.changeNext_move(CLICK_CD_MELEE)
user.visible_message("[user] knocks on [src].")
playsound(src, "pageturn", 50, 1)
else
diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed.dm
index 65b487bd05..b78826b23b 100644
--- a/code/game/turfs/closed.dm
+++ b/code/game/turfs/closed.dm
@@ -83,7 +83,7 @@
. = ..()
if(.)
switch(var_name)
- if("icon")
+ if(NAMEOF(src, icon))
SStitle.icon = icon
/turf/closed/indestructible/riveted
diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm
index bdca384bd0..758f824727 100644
--- a/code/game/turfs/simulated/floor.dm
+++ b/code/game/turfs/simulated/floor.dm
@@ -162,7 +162,7 @@
return 0
/turf/open/floor/crowbar_act(mob/living/user, obj/item/I)
- return intact ? pry_tile(I, user) : FALSE
+ return intact ? FORCE_BOOLEAN(pry_tile(I, user)) : FALSE
/turf/open/floor/proc/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
if(T.turf_type == type)
diff --git a/code/game/turfs/simulated/floor/fancy_floor.dm b/code/game/turfs/simulated/floor/fancy_floor.dm
index f940761ff8..946401ba18 100644
--- a/code/game/turfs/simulated/floor/fancy_floor.dm
+++ b/code/game/turfs/simulated/floor/fancy_floor.dm
@@ -26,7 +26,8 @@
/turf/open/floor/wood/screwdriver_act(mob/living/user, obj/item/I)
if(..())
return TRUE
- return pry_tile(I, user)
+ . = STOP_ATTACK_PROC_CHAIN
+ pry_tile(I, user)
/turf/open/floor/wood/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
if(T.turf_type == type)
diff --git a/code/game/turfs/simulated/floor/light_floor.dm b/code/game/turfs/simulated/floor/light_floor.dm
index f9376c0672..4bca25f96c 100644
--- a/code/game/turfs/simulated/floor/light_floor.dm
+++ b/code/game/turfs/simulated/floor/light_floor.dm
@@ -52,10 +52,7 @@
set_light(0)
return ..()
-/turf/open/floor/light/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/turf/open/floor/light/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!can_modify_colour)
return
if(!on)
diff --git a/code/game/turfs/simulated/floor/mineral_floor.dm b/code/game/turfs/simulated/floor/mineral_floor.dm
index f0ac0053ce..8e0230a2b0 100644
--- a/code/game/turfs/simulated/floor/mineral_floor.dm
+++ b/code/game/turfs/simulated/floor/mineral_floor.dm
@@ -149,7 +149,7 @@
if(!.)
honk()
-/turf/open/floor/mineral/bananium/attack_hand(mob/user)
+/turf/open/floor/mineral/bananium/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
.=..()
if(!.)
honk()
@@ -202,7 +202,7 @@
if(!.)
radiate()
-/turf/open/floor/mineral/uranium/attack_hand(mob/user)
+/turf/open/floor/mineral/uranium/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
.=..()
if(!.)
radiate()
diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/simulated/floor/plating.dm
index f26a4b827a..ac80bddd00 100644
--- a/code/game/turfs/simulated/floor/plating.dm
+++ b/code/game/turfs/simulated/floor/plating.dm
@@ -124,7 +124,7 @@
ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else
playsound(src, 'sound/weapons/tap.ogg', 100, TRUE) //The attack sound is muffled by the foam itself
- user.changeNext_move(CLICK_CD_MELEE)
+ user.DelayNextAction(CLICK_CD_MELEE)
user.do_attack_animation(src)
if(prob(I.force * 20 - 25))
user.visible_message("[user] smashes through [src]!", \
diff --git a/code/game/turfs/simulated/floor/plating/asteroid.dm b/code/game/turfs/simulated/floor/plating/asteroid.dm
index 1fbeee7523..8476e02d29 100644
--- a/code/game/turfs/simulated/floor/plating/asteroid.dm
+++ b/code/game/turfs/simulated/floor/plating/asteroid.dm
@@ -335,7 +335,7 @@
if(!spawned_flora) // no rocks beneath mob spawners / mobs.
SpawnMonster(T)
T.ChangeTurf(turf_type, null, CHANGETURF_IGNORE_AIR)
-
+/*
/// Spawns a random mob or megafauna in the tunnel
/turf/open/floor/plating/asteroid/airless/cave/proc/SpawnMonster(turf/T)
if(!isarea(loc))
@@ -367,7 +367,7 @@
new randumb(T)
return TRUE
-
+*/ //moved to sandcode
#undef SPAWN_MEGAFAUNA
#undef SPAWN_BUBBLEGUM
diff --git a/code/game/turfs/simulated/floor/reinf_floor.dm b/code/game/turfs/simulated/floor/reinf_floor.dm
index 7816341a0a..03045674e4 100644
--- a/code/game/turfs/simulated/floor/reinf_floor.dm
+++ b/code/game/turfs/simulated/floor/reinf_floor.dm
@@ -89,10 +89,7 @@
/turf/open/floor/engine/attack_paw(mob/user)
return attack_hand(user)
-/turf/open/floor/engine/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/turf/open/floor/engine/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
user.Move_Pulled(src)
//air filled floors; used in atmos pressure chambers
diff --git a/code/game/turfs/simulated/wall/mineral_walls.dm b/code/game/turfs/simulated/wall/mineral_walls.dm
index a5f0d5e824..ed48c24462 100644
--- a/code/game/turfs/simulated/wall/mineral_walls.dm
+++ b/code/game/turfs/simulated/wall/mineral_walls.dm
@@ -72,7 +72,7 @@
return
return
-/turf/closed/wall/mineral/uranium/attack_hand(mob/user)
+/turf/closed/wall/mineral/uranium/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
radiate()
. = ..()
@@ -192,6 +192,7 @@
icon_state = "map-shuttle"
explosion_block = 3
flags_1 = CAN_BE_DIRTY_1 | DEFAULT_RICOCHET_1
+ flags_ricochet = RICOCHET_SHINY | RICOCHET_HARD
sheet_type = /obj/item/stack/sheet/mineral/titanium
smooth = SMOOTH_MORE|SMOOTH_DIAGONAL
canSmoothWith = list(/turf/closed/wall/mineral/titanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/shuttle, /obj/structure/shuttle/engine/heater, /obj/structure/falsewall/titanium)
diff --git a/code/game/turfs/simulated/wall/reinf_walls.dm b/code/game/turfs/simulated/wall/reinf_walls.dm
index a1d2c1757c..6b41f41763 100644
--- a/code/game/turfs/simulated/wall/reinf_walls.dm
+++ b/code/game/turfs/simulated/wall/reinf_walls.dm
@@ -36,7 +36,8 @@
new /obj/item/stack/sheet/metal(src, 2)
/turf/closed/wall/r_wall/attack_animal(mob/living/simple_animal/M)
- M.changeNext_move(CLICK_CD_MELEE)
+ if(!M.CheckActionCooldown())
+ return
M.do_attack_animation(src)
if(!M.environment_smash)
return
@@ -46,6 +47,7 @@
else
playsound(src, 'sound/effects/bang.ogg', 50, 1)
to_chat(M, "This wall is far too strong for you to destroy.")
+ M.DelayNextAction()
/turf/closed/wall/r_wall/try_destroy(obj/item/I, mob/user, turf/T)
if(istype(I, /obj/item/pickaxe/drill/jackhammer))
diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm
index b01a1df2c0..b020ab87c9 100644
--- a/code/game/turfs/simulated/walls.dm
+++ b/code/game/turfs/simulated/walls.dm
@@ -6,9 +6,12 @@
icon = 'icons/turf/walls/wall.dmi'
icon_state = "wall"
explosion_block = 1
-
+ flags_1 = DEFAULT_RICOCHET_1
+ flags_ricochet = RICOCHET_HARD
thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT
heat_capacity = 312500 //a little over 5 cm thick , 312500 for 1 m by 2.5 m by 0.25 m plasteel wall
+ attack_hand_speed = 8
+ attack_hand_is_action = TRUE
baseturfs = /turf/open/floor/plating
@@ -113,41 +116,65 @@
return FALSE
/turf/closed/wall/attack_paw(mob/living/user)
- user.changeNext_move(CLICK_CD_MELEE)
return attack_hand(user)
-
/turf/closed/wall/attack_animal(mob/living/simple_animal/M)
- M.changeNext_move(CLICK_CD_MELEE)
+ if(!M.CheckActionCooldown(CLICK_CD_MELEE))
+ return
+ M.DelayNextAction()
M.do_attack_animation(src)
if((M.environment_smash & ENVIRONMENT_SMASH_WALLS) || (M.environment_smash & ENVIRONMENT_SMASH_RWALLS))
playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1)
dismantle_wall(1)
return
-/turf/closed/wall/attack_hulk(mob/user, does_attack_animation = 0)
- ..(user, 1)
+/turf/closed/wall/attack_hulk(mob/living/carbon/user)
+ ..()
+ var/obj/item/bodypart/arm = user.hand_bodyparts[user.active_hand_index]
+ if(!arm)
+ return
+ if(arm.disabled)
+ return
if(prob(hardness))
- playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1)
+ playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE)
user.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk")
+ hulk_recoil(arm, user)
dismantle_wall(1)
+
else
- playsound(src, 'sound/effects/bang.ogg', 50, 1)
+ playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
add_dent(WALL_DENT_HIT)
- to_chat(user, text("You punch the wall."))
+ user.visible_message("[user] smashes \the [src]!", \
+ "You smash \the [src]!", \
+ "You hear a booming smash!")
return TRUE
-/turf/closed/wall/attack_hand(mob/user)
- . = ..()
- if(.)
+/**
+ *Deals damage back to the hulk's arm.
+ *
+ *When a hulk manages to break a wall using their hulk smash, this deals back damage to the arm used.
+ *This is in its own proc just to be easily overridden by other wall types. Default allows for three
+ *smashed walls per arm. Also, we use CANT_WOUND here because wounds are random. Wounds are applied
+ *by hulk code based on arm damage and checked when we call break_an_arm().
+ *Arguments:
+ **arg1 is the arm to deal damage to.
+ **arg2 is the hulk
+ */
+/turf/closed/wall/proc/hulk_recoil(obj/item/bodypart/arm, mob/living/carbon/human/hulkman, var/damage = 20)
+ arm.receive_damage(brute = damage, blocked = 0, wound_bonus = CANT_WOUND)
+ var/datum/mutation/human/hulk/smasher = locate(/datum/mutation/human/hulk) in hulkman.dna.mutations
+ if(!smasher || !damage) //sanity check but also snow and wood walls deal no recoil damage, so no arm breaky
return
- user.changeNext_move(CLICK_CD_MELEE)
+ smasher.break_an_arm(arm)
+
+/turf/closed/wall/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
to_chat(user, "You push the wall but nothing happens!")
playsound(src, 'sound/weapons/genhit.ogg', 25, 1)
add_fingerprint(user)
/turf/closed/wall/attackby(obj/item/W, mob/user, params)
- user.changeNext_move(CLICK_CD_MELEE)
+ if(!user.CheckActionCooldown(CLICK_CD_MELEE))
+ return
if (!user.IsAdvancedToolUser())
to_chat(user, "You don't have the dexterity to do this!")
return
@@ -156,6 +183,7 @@
if(!isturf(user.loc))
return //can't do this stuff whilst inside objects and such
+ user.DelayNextAction()
add_fingerprint(user)
var/turf/T = user.loc //get user's location for delay checks
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
index b131a1ed70..0e028ed4af 100644
--- a/code/game/turfs/space/space.dm
+++ b/code/game/turfs/space/space.dm
@@ -133,10 +133,13 @@
else
to_chat(user, "The plating is going to need some support! Place metal rods first.")
-/turf/open/space/Entered(atom/movable/A)
- ..()
- if ((!(A) || src != A.loc))
- return
+/turf/open/space/Entered(atom/movable/A, atom/OldLoc)
+ . = ..()
+
+ var/turf/old = get_turf(OldLoc)
+ if(!isspaceturf(old) && ismob(A))
+ var/mob/M = A
+ M.update_gravity(M.mob_has_gravity())
if(destination_z && destination_x && destination_y && !(A.pulledby || !A.can_be_z_moved))
var/tx = destination_x
@@ -170,6 +173,12 @@
stoplag()//Let a diagonal move finish, if necessary
A.newtonian_move(A.inertia_dir)
+/turf/open/space/Exited(atom/movable/AM, atom/OldLoc)
+ . = ..()
+ var/turf/old = get_turf(OldLoc)
+ if(!isspaceturf(old) && ismob(AM))
+ var/mob/M = AM
+ M.update_gravity(M.mob_has_gravity())
/turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent)
return
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index b1a10c72ce..55d945535d 100755
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -122,10 +122,7 @@
requires_activation = FALSE
..()
-/turf/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/turf/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
user.Move_Pulled(src)
/turf/proc/multiz_turf_del(turf/T, dir)
diff --git a/code/game/world.dm b/code/game/world.dm
index db193f31b0..55333fb3e6 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -131,11 +131,13 @@ GLOBAL_LIST(topic_status_cache)
GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log"
GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log"
GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log"
+ GLOB.world_paper_log = "[GLOB.log_directory]/paper.log"
GLOB.tgui_log = "[GLOB.log_directory]/tgui.log"
GLOB.subsystem_log = "[GLOB.log_directory]/subsystem.log"
GLOB.reagent_log = "[GLOB.log_directory]/reagents.log"
GLOB.world_crafting_log = "[GLOB.log_directory]/crafting.log"
GLOB.click_log = "[GLOB.log_directory]/click.log"
+ GLOB.world_asset_log = "[GLOB.log_directory]/asset.log"
#ifdef UNIT_TESTS
diff --git a/code/modules/NTNet/relays.dm b/code/modules/NTNet/relays.dm
index 0d855f15bb..8c33dd1eba 100644
--- a/code/modules/NTNet/relays.dm
+++ b/code/modules/NTNet/relays.dm
@@ -9,8 +9,6 @@
icon_state = "bus"
density = TRUE
circuit = /obj/item/circuitboard/machine/ntnet_relay
- ui_x = 400
- ui_y = 300
var/datum/ntnet/NTNet = null // This is mostly for backwards reference and to allow varedit modifications from ingame.
var/enabled = 1 // Set to 0 if the relay was turned off
@@ -64,15 +62,12 @@
SSnetworks.station_network.add_log("Quantum relay switched from overload recovery mode to normal operation mode.")
..()
-/obj/machinery/ntnet_relay/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
-
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
-
+/obj/machinery/ntnet_relay/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "ntnet_relay", "NTNet Quantum Relay", ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "NtnetRelay")
ui.open()
-
/obj/machinery/ntnet_relay/ui_data(mob/user)
var/list/data = list()
data["enabled"] = enabled
@@ -81,7 +76,6 @@
data["dos_crashed"] = dos_failure
return data
-
/obj/machinery/ntnet_relay/ui_act(action, params)
if(..())
return
@@ -118,4 +112,4 @@
D.target = null
D.error = "Connection to quantum relay severed"
- return ..()
\ No newline at end of file
+ return ..()
diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm
index 26ae200990..cc59fe10ab 100644
--- a/code/modules/VR/vr_sleeper.dm
+++ b/code/modules/VR/vr_sleeper.dm
@@ -71,22 +71,22 @@
/obj/machinery/vr_sleeper/update_icon_state()
icon_state = "[initial(icon_state)][state_open ? "-open" : ""]"
-/obj/machinery/vr_sleeper/open_machine()
- if(state_open)
- return
- if(occupant)
- SStgui.close_user_uis(occupant, src)
- return ..()
/obj/machinery/vr_sleeper/MouseDrop_T(mob/target, mob/user)
if(user.lying || !iscarbon(target) || !Adjacent(target) || !user.canUseTopic(src, BE_CLOSE, TRUE, NO_TK))
return
close_machine(target)
+ ui_interact(user)
-/obj/machinery/vr_sleeper/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/vr_sleeper/ui_state(mob/user)
+ if(user == occupant)
+ return GLOB.contained_state
+ return GLOB.default_state
+
+/obj/machinery/vr_sleeper/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "vr_sleeper", "VR Sleeper", 475, 340, master_ui, state)
+ ui = new(user, src, "VrSleeper", "VR Sleeper")
ui.open()
/obj/machinery/vr_sleeper/ui_act(action, params)
@@ -130,11 +130,13 @@
/obj/machinery/vr_sleeper/ui_data(mob/user)
var/list/data = list()
+ var/is_living
if(vr_mob && !QDELETED(vr_mob))
+ is_living = isliving(vr_mob)
data["can_delete_avatar"] = TRUE
data["vr_avatar"] = list("name" = vr_mob.name)
- data["isliving"] = istype(vr_mob)
- if(data["isliving"])
+ data["isliving"] = is_living
+ if(is_living)
var/status
switch(vr_mob.stat)
if(CONSCIOUS)
@@ -146,6 +148,11 @@
if(SOFT_CRIT)
status = "Barely Conscious"
data["vr_avatar"] += list("status" = status, "health" = vr_mob.health, "maxhealth" = vr_mob.maxHealth)
+ else
+ data["can_delete_avatar"] = FALSE
+ data["vr_avatar"] = FALSE
+ data["isliving"] = FALSE
+
data["toggle_open"] = state_open
data["emagged"] = you_die_in_the_game_you_die_for_real
data["isoccupant"] = (user == occupant)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index a2e2b3c122..76e78dc381 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -25,7 +25,10 @@
to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.")
return
- var/body = "Options for [M.key]"
+//SKYRAT CHANGES BEGIN
+ var/list/body = list("Options for [M.key]")
+//SKYRAT CHANGES END
+
body += "Options panel for [M]"
if(M.client)
body += " played by [M.client] "
@@ -40,6 +43,11 @@
if(M.client)
body += " \[First Seen: [M.client.player_join_date]\]\[Byond account registered on: [M.client.account_join_date]\]"
+ body += "
CentCom Galactic Ban DB: "
+ if(CONFIG_GET(string/centcom_ban_db))
+ body += "Search"
+ else
+ body += "Disabled"
body += "
"
body += "Rudimentary transformation: These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible. "
@@ -199,7 +207,12 @@
body += " "
body += ""
- usr << browse(body, "window=adminplayeropts-[REF(M)];size=550x515")
+//SKYRAT CHANGES BEGIN
+ var/datum/browser/popup = new(usr, "adminplayeropts-[REF(M)]", "Player Panel", nwidth = 550, nheight = 515)
+ popup.set_content(body.Join())
+ popup.open()
+//SKYRAT CHANGES END
+
SSblackbox.record_feedback("tally", "admin_verb", 1, "Player Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -592,6 +605,11 @@
set category = "Server"
set desc="Start the round RIGHT NOW"
set name="Start Now"
+ if(!isnull(usr.client.address)) //skyrat edit - confirm early game start unless connecting locally
+ message_admins("[key_name(usr)] is deciding to start the game early")
+ if(alert(usr, "Start game NOW?", "Game Start Confirmation", "Yes", "No")!= "Yes")
+ message_admins("[key_name(usr)] has cancelled starting the game early")
+ return FALSE //end skyrat edit
if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP)
SSticker.start_immediately = TRUE
log_admin("[usr.key] has started the game.")
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 289abbe250..718f9d4246 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -62,7 +62,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/cmd_admin_check_player_exp, /* shows players by playtime */
/client/proc/toggle_combo_hud, // toggle display of the combination pizza antag and taco sci/med/eng hud
/client/proc/toggle_AI_interact, /*toggle admin ability to interact with machines as an AI*/
- /client/proc/open_shuttle_manipulator, /* Opens shuttle manipulator UI */
+ /datum/admins/proc/open_shuttlepanel, /* Opens shuttle manipulator UI */
/client/proc/respawn_character,
/client/proc/secrets,
/client/proc/toggle_hear_radio, /*allows admins to hide all radio output*/
diff --git a/code/modules/admin/chat_commands.dm b/code/modules/admin/chat_commands.dm
index 7664d85b7d..bab74300f5 100644
--- a/code/modules/admin/chat_commands.dm
+++ b/code/modules/admin/chat_commands.dm
@@ -150,7 +150,7 @@ GLOBAL_LIST(round_end_notifiees)
/datum/tgs_chat_command/wheelofsalt/Run(datum/tgs_chat_user/sender, params)
var/saltresult = "The wheel of salt [pick("clatters","screams","vibrates","clanks","resonates","groans","moans","squeaks","emits a[pick(" god-forsaken"," lewd"," creepy"," generic","n orgasmic"," demonic")] [pick("airhorn","bike horn","trumpet","clown","latex","vore","dog","laughing")] noise")] as it spins violently... And it seems the salt of the day is the "
- var/saltprimarysubject = "[pick("combat","medical","grab","furry","wall","orgasm","cat","ERP","lizard","dog","latex","vision cone","atmospherics","table","chem","vore","dogborg","Skylar Lineman","Mekhi Anderson","Peppermint","rework","cum","dick","cockvore","Medihound","sleeper","belly sleeper","door wires","flightsuit","coder privilege","Developer abuse","ban reason","github self merge","red panda","beret","male catgirl","powergame","hexacrocin","Discord server","Clitadel","Cargonia","Solarian Republic","Main and RP merger","bluespace","salt","chem dispenser theft","Botany","moth","BWOINK","anal vore","stamina","Mason Jakops","mining","noodle","milf","Lavaland","Necropolis","Ashwalker","Chase Redtail","Drew Mint","Pavel Marsk","Joker Amari","Durgit","chaplain","Antag","nanite","Syndicate","Nar-Sie","Ratvar","Cult","maint","Foam-Force","AI","cyborg","ghost","clockwork","cyberpunk","vaporwave","Clown","Leon Beech","Mime","security","research","Megafauna","Bubblegum","Ash Drake","Legion","Colossus","White Shuttle","Changeling","Cowboy","Space Ninja","Poly","Revolutionary","Skyrim","forbidden fruits","xenomorph","blob","Nuclear Operative","crossdressing")]"
+ var/saltprimarysubject = "[pick("combat","medical","grab","furry","wall","orgasm","cat","ERP","lizard","dog","latex","vision cone","atmospherics","table","chem","vore","dogborg","Skylar Lineman","Mekhi Anderson","Peppermint","rework","cum","dick","cockvore","Medihound","sleeper","belly sleeper","door wires","flightsuit","coder privilege","Developer abuse","ban reason","github self merge","red panda","beret","male catgirl","powergame","hexacrocin removal","Discord server","Clitadel","Cargonia","Solarian Republic","Main and RP merger","bluespace","salt","chem dispenser theft","Botany","moth","BWOINK","anal vore","stamina","Mason Jakops","mining","noodle","milf","Lavaland","Necropolis","Ashwalker","Chase Redtail","Drew Mint","Pavel Marsk","Joker Amari","Durgit","chaplain","Antag","nanite","Syndicate","Nar-Sie","Ratvar","Cult","maint","Foam-Force","AI","cyborg","ghost","clockwork","cyberpunk","vaporwave","Clown","Leon Beech","Mime","security","research","Megafauna","Bubblegum","Ash Drake","Legion","Colossus","White Shuttle","Changeling","Cowboy","Space Ninja","Poly","Revolutionary","Skyrim","forbidden fruits","xenomorph","blob","Nuclear Operative","crossdressing")]"
var/saltsecondarysubject = "[pick("rework","changes","r34","ban","removal","addition","leak","proposal","fanart","introduction","tabling","ERP","bikeshedding","crossdressing","sprites","semen keg","argument","theft","nerf","screeching","salt","creampie","lewding","murder","kissing","marriage","replacement","fucking","ship","netflix adaptation","dance","remaster","system","voyeur","decoration","pre-order","bukkake","seduction","worship","gangbang","handholding")]"
if(prob(10))
saltresult += "@here for your salt, all day every day"
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 042a6e0fe9..13e8877440 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -863,7 +863,7 @@
if(jobban_isbanned(M, ROLE_TRAITOR) || isbanned_dept)
dat += "
"
+ for(var/list/ban in bans)
+ dat += "Server: [sanitize(ban["sourceName"])] "
+ dat += "RP Level: [sanitize(ban["sourceRoleplayLevel"])] "
+ dat += "Type: [sanitize(ban["type"])] "
+ dat += "Banned By: [sanitize(ban["bannedBy"])] "
+ dat += "Reason: [sanitize(ban["reason"])] "
+ dat += "Datetime: [sanitize(ban["bannedOn"])] "
+ var/expiration = ban["expires"]
+ dat += "Expires: [expiration ? "[sanitize(expiration)]" : "Permanent"] "
+ if(ban["type"] == "job")
+ dat += "Jobs: "
+ var/list/jobs = ban["jobs"]
+ dat += sanitize(jobs.Join(", "))
+ dat += " "
+ dat += ""
+
+ dat += " "
+ var/datum/browser/popup = new(usr, "centcomlookup-[ckey]", "
Central Command Galactic Ban Database
", 700, 600)
+ popup.set_content(dat.Join())
+ popup.open(0)
+
else if(href_list["modantagrep"])
if(!check_rights(R_ADMIN))
return
diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
index 044016698c..b757028ea2 100644
--- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm
+++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
@@ -666,8 +666,8 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
obj_count_finished = select_refs
var/n = 0
for(var/i in found)
- if(++n == 20000)
- text_list += " TRUNCATED - 20000 OBJECT LIMIT HIT"
+ if(++n == 2500)
+ text_list += " TRUNCATED - 2500 OBJECT LIMIT HIT"
SDQL_print(i, text_list, print_nulls)
select_refs[REF(i)] = TRUE
SDQL2_TICK_CHECK
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 14cf0d7ee2..b93c8dabf1 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -371,8 +371,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
return
var/msg = "- AdminHelp marked as IC issue by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]! - "
- msg += "Losing is part of the game! "
- msg += "It is also possible that your ahelp is unable to be answered properly, due to events occurring in the round."
+ msg += "Your ahelp is unable to be answered properly due to events occurring in the round. Your question probably has an IC answer, which means you should deal with it IC!"
if(initiator)
to_chat(initiator, msg)
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index bbd6b55e4f..68b9a8e341 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -55,7 +55,7 @@
if(AH)
message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.")
- var/msg = input(src,"Message:", "Private message to [key_name(C, 0, 0)]") as message|null
+ var/msg = stripped_multiline_input(src,"Message:", "Private message to [key_name(C, 0, 0)]")
if (!msg)
message_admins("[key_name_admin(src)] has cancelled their reply to [key_name(C, 0, 0)]'s admin help.")
return
@@ -87,10 +87,10 @@
if(irc)
- if(!ircreplyamount) //to prevent people from spamming irc
+ if(!ircreplyamount) //to prevent people from spamming irc/discord
return
if(!msg)
- msg = input(src,"Message:", "Private message to Administrator") as text|null
+ msg = stripped_multiline_input(src,"Message:", "Private message to Administrator")
if(!msg)
return
@@ -112,7 +112,7 @@
//get message text, limit it's length.and clean/escape html
if(!msg)
- msg = input(src,"Message:", "Private message to [key_name(recipient, 0, 0)]") as message|null
+ msg = stripped_multiline_input(src,"Message:", "Private message to [key_name(recipient, 0, 0)]")
msg = trim(msg)
if(!msg)
return
@@ -191,7 +191,7 @@
spawn() //so we don't hold the caller proc up
var/sender = src
var/sendername = key
- var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as text|null //show message and await a reply
+ var/reply = stripped_multiline_input(recipient, msg,"Admin PM from-[sendername]", "") //show message and await a reply
if(recipient && reply)
if(sender)
recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them
diff --git a/code/modules/admin/verbs/borgpanel.dm b/code/modules/admin/verbs/borgpanel.dm
index 869e44e4f5..35f4ddb3e5 100644
--- a/code/modules/admin/verbs/borgpanel.dm
+++ b/code/modules/admin/verbs/borgpanel.dm
@@ -36,7 +36,7 @@
/datum/borgpanel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "borgopanel", "Borg Panel", 700, 700, master_ui, state)
+ ui = new(user, src, ui_key, "BorgPanel", "Borg Panel", 700, 700, master_ui, state)
ui.open()
/datum/borgpanel/ui_data(mob/user)
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index f068f05a4a..5bb8c4ff15 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -502,7 +502,7 @@
dresscode = outfits[dresscode]
if(dresscode == "Cancel")
- return
+ return FALSE // Skyrat change
if (dresscode == "As Job...")
var/list/job_paths = subtypesof(/datum/outfit/job)
diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm
index b3bea2201c..6f8e9e703f 100644
--- a/code/modules/admin/verbs/diagnostics.dm
+++ b/code/modules/admin/verbs/diagnostics.dm
@@ -22,37 +22,6 @@
show_air_status_to(target, usr)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Air Status") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/client/proc/fix_next_move()
- set category = "Debug"
- set name = "Unfreeze Everyone"
- var/largest_move_time = 0
- var/largest_click_time = 0
- var/mob/largest_move_mob = null
- var/mob/largest_click_mob = null
- for(var/mob/M in world)
- if(!M.client)
- continue
- if(M.next_move >= largest_move_time)
- largest_move_mob = M
- if(M.next_move > world.time)
- largest_move_time = M.next_move - world.time
- else
- largest_move_time = 1
- if(M.next_click >= largest_click_time)
- largest_click_mob = M
- if(M.next_click > world.time)
- largest_click_time = M.next_click - world.time
- else
- largest_click_time = 0
- log_admin("DEBUG: [key_name(M)] next_move = [M.next_move] lastDblClick = [M.next_click] world.time = [world.time]")
- M.next_move = 1
- M.next_click = 0
- message_admins("[ADMIN_LOOKUPFLW(largest_move_mob)] had the largest move delay with [largest_move_time] frames / [DisplayTimeText(largest_move_time)]!")
- message_admins("[ADMIN_LOOKUPFLW(largest_click_mob)] had the largest click delay with [largest_click_time] frames / [DisplayTimeText(largest_click_time)]!")
- message_admins("world.time = [world.time]")
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Unfreeze Everyone") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- return
-
/client/proc/radio_report()
set category = "Debug"
set name = "Radio report"
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index d335cfb171..3bcaa89624 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -421,7 +421,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
new_character.real_name = record_found.fields["name"]
new_character.gender = record_found.fields["gender"]
new_character.age = record_found.fields["age"]
- new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"])
+ new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], null, record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"])
else
var/datum/preferences/A = new()
A.copy_to(new_character)
@@ -811,6 +811,19 @@ Traitors and the like can also be revived with the previous role mostly intact.
message_admins("Admin [key_name_admin(usr)] has disabled random events.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Random Events", "[new_are ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+/client/proc/admin_enable_disk_inactive_msg()
+ set category = "Server"
+ set name = "Toggle nuke disk stationary admin logging"
+ set desc = "Toggles nuke disk stationary admin logs for all admins"
+ var/current_state = !CONFIG_GET(flag/admin_disk_inactive_msg)
+ CONFIG_SET(flag/admin_disk_inactive_msg, current_state)
+ if(current_state)
+ to_chat(usr, "Inactive disk admin logging enabled")
+ message_admins("Admin [key_name_admin(usr)] has enabled inactive disk admin logging")
+ else
+ to_chat(usr, "Inactive disk admin logging disabled")
+ message_admins("Admin [key_name_admin(usr)] has disabled inactive disk admin logging")
+ SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle nuke disk stationary admin logging", "[current_state ? "Enabled" : "Disabled"]"))
/client/proc/admin_change_sec_level()
set category = "Special Verbs"
@@ -1058,13 +1071,6 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
var/datum/atom_hud/A = GLOB.huds[ANTAG_HUD_TRAITOR]
return A.hudusers[mob]
-/client/proc/open_shuttle_manipulator()
- set category = "Admin"
- set name = "Shuttle Manipulator"
- set desc = "Opens the shuttle manipulator UI."
-
- for(var/obj/machinery/shuttle_manipulator/M in GLOB.machines)
- M.ui_interact(usr)
/client/proc/run_weather()
set category = "Fun"
@@ -1276,7 +1282,23 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
if(!check_rights(R_ADMIN) || !check_rights(R_FUN))
return
- var/list/punishment_list = list(ADMIN_PUNISHMENT_PIE, ADMIN_PUNISHMENT_CUSTOM_PIE, ADMIN_PUNISHMENT_FIREBALL, ADMIN_PUNISHMENT_LIGHTNING, ADMIN_PUNISHMENT_BRAINDAMAGE, ADMIN_PUNISHMENT_BSA, ADMIN_PUNISHMENT_GIB, ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, ADMIN_PUNISHMENT_SUPPLYPOD, ADMIN_PUNISHMENT_MAZING, ADMIN_PUNISHMENT_ROD)
+ var/list/punishment_list = list(ADMIN_PUNISHMENT_PIE,
+ ADMIN_PUNISHMENT_CUSTOM_PIE,
+ ADMIN_PUNISHMENT_FIREBALL,
+ ADMIN_PUNISHMENT_LIGHTNING,
+ ADMIN_PUNISHMENT_BRAINDAMAGE,
+ ADMIN_PUNISHMENT_BSA,
+ ADMIN_PUNISHMENT_GIB,
+ ADMIN_PUNISHMENT_SUPPLYPOD_QUICK,
+ ADMIN_PUNISHMENT_SUPPLYPOD,
+ ADMIN_PUNISHMENT_MAZING,
+ ADMIN_PUNISHMENT_ROD,
+ ADMIN_PUNISHMENT_SHOES,
+ ADMIN_PUNISHMENT_PICKLE,
+ ADMIN_PUNISHMENT_FRY,
+ ADMIN_PUNISHMENT_CRACK,
+ ADMIN_PUNISHMENT_BLEED,
+ ADMIN_PUNISHMENT_SCARIFY)
var/punishment = input("Choose a punishment", "DIVINE SMITING") as null|anything in punishment_list
@@ -1341,7 +1363,7 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
if(ADMIN_PUNISHMENT_PIE)
var/obj/item/reagent_containers/food/snacks/pie/cream/nostun/creamy = new(get_turf(target))
creamy.splat(target)
- if (ADMIN_PUNISHMENT_CUSTOM_PIE)
+ if(ADMIN_PUNISHMENT_CUSTOM_PIE)
var/obj/item/reagent_containers/food/snacks/pie/cream/nostun/A = new()
if(!A.reagents)
var/amount = input(usr, "Specify the reagent size of [A]", "Set Reagent Size", 50) as num|null
@@ -1354,9 +1376,122 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
if(amount)
A.reagents.add_reagent(chosen_id, amount)
A.splat(target)
+ if(ADMIN_PUNISHMENT_CRACK)
+ if(!iscarbon(target))
+ to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
+ return
+ var/mob/living/carbon/C = target
+ for(var/i in C.bodyparts)
+ var/obj/item/bodypart/squish_part = i
+ var/type_wound = pick(list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate))
+ squish_part.force_wound_upwards(type_wound, smited=TRUE)
+ if(ADMIN_PUNISHMENT_BLEED)
+ if(!iscarbon(target))
+ to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
+ return
+ var/mob/living/carbon/C = target
+ for(var/i in C.bodyparts)
+ var/obj/item/bodypart/slice_part = i
+ var/type_wound = pick(list(/datum/wound/slash/severe, /datum/wound/slash/moderate))
+ slice_part.force_wound_upwards(type_wound, smited=TRUE)
+ type_wound = pick(list(/datum/wound/slash/critical, /datum/wound/slash/severe, /datum/wound/slash/moderate))
+ slice_part.force_wound_upwards(type_wound, smited=TRUE)
+ type_wound = pick(list(/datum/wound/slash/critical, /datum/wound/slash/severe))
+ slice_part.force_wound_upwards(type_wound, smited=TRUE)
+ if(ADMIN_PUNISHMENT_SCARIFY)
+ if(!iscarbon(target))
+ to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
+ return
+ var/mob/living/carbon/dude = target
+ dude.generate_fake_scars(rand(1, 4))
+ to_chat(dude, "You feel your body grow jaded and torn...")
+ if(ADMIN_PUNISHMENT_PERFORATE)
+ if(!iscarbon(target))
+ to_chat(usr,"This must be used on a carbon mob.", confidential = TRUE)
+ return
+
+ var/list/how_fucked_is_this_dude = list("A little", "A lot", "So fucking much", "FUCK THIS DUDE")
+ var/hatred = input("How much do you hate this guy?") in how_fucked_is_this_dude
+ var/repetitions
+ var/shots_per_limb_per_rep = 2
+ var/damage
+ switch(hatred)
+ if("A little")
+ repetitions = 1
+ damage = 5
+ if("A lot")
+ repetitions = 2
+
+ damage = 8
+ if("So fucking much")
+ repetitions = 3
+ damage = 10
+ if("FUCK THIS DUDE")
+ repetitions = 4
+ damage = 10
+
+ var/mob/living/carbon/dude = target
+ var/list/open_adj_turfs = get_adjacent_open_turfs(dude)
+ var/list/wound_bonuses = list(15, 70, 110, 250)
+
+ var/delay_per_shot = 1
+ var/delay_counter = 1
+
+ dude.Immobilize(5 SECONDS)
+ for(var/wound_bonus_rep in 1 to repetitions)
+ for(var/i in dude.bodyparts)
+ var/obj/item/bodypart/slice_part = i
+ var/shots_this_limb = 0
+ for(var/t in shuffle(open_adj_turfs))
+ var/turf/iter_turf = t
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/firing_squad, dude, iter_turf, slice_part.body_zone, wound_bonuses[wound_bonus_rep], damage), delay_counter)
+ delay_counter += delay_per_shot
+ shots_this_limb++
+ if(shots_this_limb > shots_per_limb_per_rep)
+ break
+ if(ADMIN_PUNISHMENT_PICKLE)
+ target.turn_into_pickle()
+ if(ADMIN_PUNISHMENT_FRY)
+ target.fry()
+
+ if(ADMIN_PUNISHMENT_SHOES)
+ if(!iscarbon(target))
+ to_chat(usr,"This must be used on a carbon mob.")
+ return
+ var/mob/living/carbon/C = target
+ var/obj/item/clothing/shoes/sick_kicks = C.shoes
+ if(!sick_kicks?.can_be_tied)
+ to_chat(usr,"[C] does not have knottable shoes!")
+ return
+ sick_kicks.adjust_laces(SHOES_KNOTTED)
punish_log(target, punishment)
+/**
+ * firing_squad is a proc for the :B:erforate smite to shoot each individual bullet at them, so that we can add actual delays without sleep() nonsense
+ *
+ * Hilariously, if you drag someone away mid smite, the bullets will still chase after them from the original spot, possibly hitting other people. Too funny to fix imo
+ *
+ * Arguments:
+ * * target- guy we're shooting obviously
+ * * source_turf- where the bullet begins, preferably on a turf next to the target
+ * * body_zone- which bodypart we're aiming for, if there is one there
+ * * wound_bonus- the wounding power we're assigning to the bullet, since we don't care about the base one
+ * * damage- the damage we're assigning to the bullet, since we don't care about the base one
+ */
+/proc/firing_squad(mob/living/carbon/target, turf/source_turf, body_zone, wound_bonus, damage)
+ if(!target.get_bodypart(body_zone))
+ return
+ playsound(target, 'sound/weapons/shot.ogg', 100)
+ var/obj/item/projectile/bullet/smite/divine_wrath = new(source_turf)
+ divine_wrath.damage = damage
+ divine_wrath.wound_bonus = wound_bonus
+ divine_wrath.original = target
+ divine_wrath.def_zone = body_zone
+ divine_wrath.spread = 0
+ divine_wrath.preparePixelProjectile(target, source_turf)
+ divine_wrath.fire()
+
/client/proc/punish_log(var/whom, var/punishment)
var/msg = "[key_name_admin(usr)] punished [key_name_admin(whom)] with [punishment]."
message_admins(msg)
diff --git a/code/modules/admin/verbs/shuttlepanel.dm b/code/modules/admin/verbs/shuttlepanel.dm
new file mode 100644
index 0000000000..a80eafae02
--- /dev/null
+++ b/code/modules/admin/verbs/shuttlepanel.dm
@@ -0,0 +1,76 @@
+/datum/admins/proc/open_shuttlepanel()
+ set category = "Admin"
+ set name = "Shuttle Manipulator"
+ set desc = "Opens the shuttle manipulator UI."
+
+ if(!check_rights(R_ADMIN))
+ return
+
+ SSshuttle.ui_interact(usr)
+
+
+/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user)
+ var/list/options = list()
+
+ for(var/port in SSshuttle.stationary)
+ if (istype(port, /obj/docking_port/stationary/transit))
+ continue // please don't do this
+ var/obj/docking_port/stationary/S = port
+ if (canDock(S) == SHUTTLE_CAN_DOCK)
+ options[S.name || S.id] = S
+
+ options += "--------"
+ options += "Infinite Transit"
+ options += "Delete Shuttle"
+ options += "Into The Sunset (delete & greentext 'escape')"
+
+ var/selection = input(user, "Select where to fly [name || id]:", "Fly Shuttle") as null|anything in options
+ if(!selection)
+ return
+
+ switch(selection)
+ if("Infinite Transit")
+ destination = null
+ mode = SHUTTLE_IGNITING
+ setTimer(ignitionTime)
+
+ if("Delete Shuttle")
+ if(alert(user, "Really delete [name || id]?", "Delete Shuttle", "Cancel", "Really!") != "Really!")
+ return
+ jumpToNullSpace()
+
+ if("Into The Sunset (delete & greentext 'escape')")
+ if(alert(user, "Really delete [name || id] and greentext escape objectives?", "Delete Shuttle", "Cancel", "Really!") != "Really!")
+ return
+ intoTheSunset()
+
+ else
+ if(options[selection])
+ request(options[selection])
+
+/obj/docking_port/mobile/emergency/admin_fly_shuttle(mob/user)
+ return // use the existing verbs for this
+
+/obj/docking_port/mobile/arrivals/admin_fly_shuttle(mob/user)
+ switch(alert(user, "Would you like to fly the arrivals shuttle once or change its destination?", "Fly Shuttle", "Fly", "Retarget", "Cancel"))
+ if("Cancel")
+ return
+ if("Fly")
+ return ..()
+
+ var/list/options = list()
+
+ for(var/port in SSshuttle.stationary)
+ if (istype(port, /obj/docking_port/stationary/transit))
+ continue // please don't do this
+ var/obj/docking_port/stationary/S = port
+ if (canDock(S) == SHUTTLE_CAN_DOCK)
+ options[S.name || S.id] = S
+
+ var/selection = input(user, "Select the new arrivals destination:", "Fly Shuttle") as null|anything in options
+ if(!selection)
+ return
+ target_dock = options[selection]
+ if(!QDELETED(target_dock))
+ destination = target_dock
+
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 9fa8145339..8bff37ab03 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -23,6 +23,7 @@ GLOBAL_LIST_EMPTY(antagonists)
var/show_name_in_check_antagonists = FALSE //Will append antagonist name in admin listings - use for categories that share more than one antag type
var/list/blacklisted_quirks = list(/datum/quirk/nonviolent,/datum/quirk/mute) // Quirks that will be removed upon gaining this antag. Pacifist and mute are default.
var/threat = 0 // Amount of threat this antag poses, for dynamic mode
+ var/show_to_ghosts = FALSE // Should this antagonist be shown as antag to ghosts? Shouldn't be used for stealthy antagonists like traitors
var/list/skill_modifiers
diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm
index 9132288415..7eb7ec2af2 100644
--- a/code/modules/antagonists/abductor/abductor.dm
+++ b/code/modules/antagonists/abductor/abductor.dm
@@ -7,6 +7,7 @@
job_rank = ROLE_ABDUCTOR
show_in_antagpanel = FALSE //should only show subtypes
threat = 5
+ show_to_ghosts = TRUE
var/datum/team/abductor_team/team
var/sub_role
var/outfit
@@ -59,8 +60,10 @@
/datum/antagonist/abductor/greet()
to_chat(owner.current, "You are the [owner.special_role]!")
- to_chat(owner.current, "With the help of your teammate, kidnap and experiment on station crew members!")
- to_chat(owner.current, "Try not to disturb the habitat, it could lead to dead specimens.")
+ to_chat(owner.current, "You are an operative for your home planet's government. Your mission is to detain, experiment, and observe.")
+ to_chat(owner.current, "Work together with your teammate to bring live subjects from the space station nearby onto your ship for experimentation.")
+ to_chat(owner.current, "For the sake of the mission, do not damage the integrity of the station, do not kill anyone unless in self defense, always capture specimens first if you can, and do not steal equipment or belongings from abducted specimens.")
+ to_chat(owner.current, "Your task is to observe and take notes of the effects of your experiments.")
to_chat(owner.current, "[greet_text]")
owner.announce_objectives()
diff --git a/code/modules/antagonists/abductor/equipment/orderable_gear.dm b/code/modules/antagonists/abductor/equipment/orderable_gear.dm
new file mode 100644
index 0000000000..d15e450c99
--- /dev/null
+++ b/code/modules/antagonists/abductor/equipment/orderable_gear.dm
@@ -0,0 +1,79 @@
+GLOBAL_LIST_INIT(abductor_gear, subtypesof(/datum/abductor_gear))
+
+/datum/abductor_gear
+ /// Name of the gear
+ var/name = "Generic Abductor Gear"
+ /// Description of the gear
+ var/description = "Generic description."
+ /// Unique ID of the gear
+ var/id = "abductor_generic"
+ /// Credit cost of the gear
+ var/cost = 1
+ /// Build path of the gear itself
+ var/build_path = null
+ /// Category of the gear
+ var/category = "Basic Gear"
+
+/datum/abductor_gear/agent_helmet
+ name = "Agent Helmet"
+ description = "Abduct with style - spiky style. Prevents digital tracking."
+ id = "agent_helmet"
+ build_path = /obj/item/clothing/head/helmet/abductor
+
+/datum/abductor_gear/agent_vest
+ name = "Agent Vest"
+ description = "A vest outfitted with advanced stealth technology. It has two modes - combat and stealth."
+ id = "agent_vest"
+ build_path = /obj/item/clothing/suit/armor/abductor/vest
+
+/datum/abductor_gear/radio_silencer
+ name = "Radio Silencer"
+ description = "A compact device used to shut down communications equipment."
+ id = "radio_silencer"
+ build_path = /obj/item/abductor/silencer
+
+/datum/abductor_gear/science_tool
+ name = "Science Tool"
+ description = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras."
+ id = "science_tool"
+ build_path = /obj/item/abductor/gizmo
+/*
+/datum/abductor_gear/advanced_baton
+ name = "Advanced Baton"
+ description = "A quad-mode baton used for incapacitation and restraining of specimens."
+ id = "advanced_baton"
+ cost = 2
+ build_path = /obj/item/melee/baton/abductor //does not exist?
+*/
+/datum/abductor_gear/superlingual_matrix
+ name = "Superlingual Matrix"
+ description = "A mysterious structure that allows for instant communication between users. Pretty impressive until you need to eat something."
+ id = "superlingual_matrix"
+ build_path = /obj/item/organ/tongue/abductor
+ category = "Advanced Gear"
+
+/datum/abductor_gear/mental_interface
+ name = "Mental Interface Device"
+ description = "A dual-mode tool for directly communicating with sentient brains. It can be used to send a direct message to a target, \
+ or to send a command to a test subject with a charged gland."
+ id = "mental_interface"
+ cost = 2
+ build_path = /obj/item/abductor/mind_device
+ category = "Advanced Gear"
+
+/datum/abductor_gear/reagent_synthesizer
+ name = "Reagent Synthesizer"
+ description = "Synthesizes a variety of reagents using proto-matter."
+ id = "reagent_synthesizer"
+ cost = 2
+ build_path = /obj/item/abductor_machine_beacon/chem_dispenser
+ category = "Advanced Gear"
+
+/datum/abductor_gear/shrink_ray
+ name = "Shrink Ray Blaster"
+ description = "This is a piece of frightening alien tech that enhances the magnetic pull of atoms in a localized space to temporarily make an object shrink. \
+ That or it's just space magic. Either way, it shrinks stuff."
+ id = "shrink_ray"
+ cost = 2
+ build_path = /obj/item/gun/energy/shrink_ray
+ category = "Advanced Gear"
diff --git a/code/modules/antagonists/abductor/machinery/console.dm b/code/modules/antagonists/abductor/machinery/console.dm
index b8d5b1bf6d..f06f4fe68a 100644
--- a/code/modules/antagonists/abductor/machinery/console.dm
+++ b/code/modules/antagonists/abductor/machinery/console.dm
@@ -23,102 +23,105 @@
var/obj/machinery/abductor/pad/pad
var/obj/machinery/computer/camera_advanced/abductor/camera
var/list/datum/icon_snapshot/disguises = list()
+ /// Currently selected gear category
+ var/selected_cat
+ /// Dictates if the compact mode of the interface is on or off
+ var/compact_mode = FALSE
+ /// Possible gear to be dispensed
+ var/list/possible_gear
-/obj/machinery/abductor/console/attack_hand(mob/user)
- . = ..()
- if(.)
- return
+/obj/machinery/abductor/console/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!HAS_TRAIT(user, TRAIT_ABDUCTOR_TRAINING) && !HAS_TRAIT(user.mind, TRAIT_ABDUCTOR_TRAINING))
to_chat(user, "You start mashing alien buttons at random!")
if(do_after(user,100, target = src))
TeleporterSend()
- return
- user.set_machine(src)
- var/dat = ""
- dat += "
Abductsoft 3000
"
+/obj/machinery/abductor/console/ui_status(mob/user)
+ if(!isabductor(user) && !isobserver(user))
+ return UI_CLOSE
+ return ..()
+
+/obj/machinery/abductor/console/ui_state(mob/user)
+ return GLOB.physical_state
+
+/obj/machinery/abductor/console/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AbductorConsole", name)
+ ui.open()
+
+/obj/machinery/abductor/console/ui_static_data(mob/user)
+ var/list/data = list()
+ data["categories"] = list()
+ for(var/category in possible_gear)
+ var/list/cat = list(
+ "name" = category,
+ "items" = (category == selected_cat ? list() : null))
+ for(var/gear in possible_gear[category])
+ var/datum/abductor_gear/AG = possible_gear[category][gear]
+ cat["items"] += list(list(
+ "name" = AG.name,
+ "cost" = AG.cost,
+ "desc" = AG.description,
+ ))
+ data["categories"] += list(cat)
+ return data
+
+/obj/machinery/abductor/console/ui_data(mob/user)
+ var/list/data = list()
+ data["compactMode"] = compact_mode
+ data["experiment"] = experiment ? TRUE : FALSE
if(experiment)
- var/points = experiment.points
- var/credits = experiment.credits
- dat += "Collected Samples : [points] "
- dat += "Gear Credits: [credits] "
- dat += "Transfer data in exchange for supplies: "
- dat += "Advanced Baton (2 Credits) "
- dat += "Mental Interface Device (2 Credits) "
- dat += "Reagent Synthesizer (2 Credits) "
- dat += "Agent Helmet (1 Credit) "
- dat += "Agent Vest (1 Credit) "
- dat += "Radio Silencer (1 Credit) "
- dat += "Science Tool (1 Credit) "
- dat += "Superlingual Matrix (1 Credit) "
- else
- dat += "NO EXPERIMENT MACHINE DETECTED "
-
+ data["points"] = experiment.points
+ data["credits"] = experiment.credits
+ data["pad"] = pad ? TRUE : FALSE
if(pad)
- dat += "Emergency Teleporter System."
- dat += "Consider using primary observation console first."
- dat += "Activate Teleporter "
- if(gizmo && gizmo.marked)
- dat += "Retrieve Mark "
- else
- dat += "Retrieve Mark "
- else
- dat += "NO TELEPAD DETECTED"
-
+ data["gizmo"] = gizmo && gizmo.marked ? TRUE : FALSE
+ data["vest"] = vest ? TRUE : FALSE
if(vest)
- dat += "
"
- else
- dat += "Experiment "
-
- if(!occupant)
- dat += "
Machine Unoccupied
"
- else
- dat += "
Subject Status :
"
- dat += "[occupant.name] => "
- var/mob/living/mob_occupant = occupant
- switch(mob_occupant.stat)
- if(CONSCIOUS)
- dat += "Conscious"
- if(UNCONSCIOUS)
- dat += "Unconscious"
- else // DEAD
- dat += "Deceased"
- dat += " "
- dat += "[flash]"
- dat += " "
- dat += "Scan"
- dat += "Close" : "open=1'>Open"]"
- var/datum/browser/popup = new(user, "experiment", "Probing Console", 300, 300)
- popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
- popup.set_content(dat)
- popup.open()
-
-/obj/machinery/abductor/experiment/Topic(href, href_list)
- if(..() || usr == occupant)
- return
- usr.set_machine(src)
- if(href_list["refresh"])
- updateUsrDialog()
- return
- if(href_list["open"])
- open_machine()
- return
- if(href_list["close"])
- close_machine()
- return
+/obj/machinery/abductor/experiment/ui_data(mob/user)
+ var/list/data = list()
+ data["open"] = state_open
+ data["feedback"] = flash
+ data["occupant"] = occupant ? TRUE : FALSE
+ data["occupant_name"] = null
+ data["occupant_status"] = null
if(occupant)
var/mob/living/mob_occupant = occupant
- if(mob_occupant.stat != DEAD)
- if(href_list["experiment"])
- flash = Experiment(occupant,href_list["experiment"],usr)
- updateUsrDialog()
- add_fingerprint(usr)
+ data["occupant_name"] = mob_occupant.name
+ data["occupant_status"] = mob_occupant.stat
+ return data
-/obj/machinery/abductor/experiment/proc/Experiment(mob/occupant,type,mob/user)
+/obj/machinery/abductor/experiment/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("door")
+ if(state_open)
+ close_machine()
+ return TRUE
+ else
+ open_machine()
+ return TRUE
+ if("experiment")
+ if(!occupant)
+ return
+ var/mob/living/mob_occupant = occupant
+ if(mob_occupant.stat == DEAD)
+ return
+ flash = experiment(occupant, params["experiment_type"], usr)
+ return TRUE
+
+/**
+ * experiment: Performs selected experiment on occupant mob, resulting in a point reward on success
+ *
+ * Arguments:
+ * * occupant The mob inside the machine
+ * * type The type of experiment to be performed
+ * * user The mob starting the experiment
+ */
+/obj/machinery/abductor/experiment/proc/experiment(mob/occupant, type, mob/user)
LAZYINITLIST(history)
var/mob/living/carbon/human/H = occupant
var/datum/antagonist/abductor/user_abductor = user.mind.has_antag_datum(/datum/antagonist/abductor)
if(!user_abductor)
- return "Authorization failure. Contact mothership immidiately."
+ return "Authorization failure. Contact mothership immediately."
var/point_reward = 0
+ if(!H)
+ return "Invalid or missing specimen."
if(H in history)
- return "Specimen already in database."
+ return "Specimen already in database."
if(H.stat == DEAD)
say("Specimen deceased - please provide fresh sample.")
- return "Specimen deceased."
+ return "Specimen deceased."
var/obj/item/organ/heart/gland/GlandTest = locate() in H.internal_organs
if(!GlandTest)
say("Experimental dissection not detected!")
- return "No glands detected!"
- if(H.mind != null && (H.voluntary_ghosted || (H.ckey != null)))
+ return "No glands detected!"
+ if(H.mind != null && H.ckey != null)
LAZYINITLIST(abductee_minds)
LAZYADD(history, H)
LAZYADD(abductee_minds, H.mind)
@@ -196,22 +150,27 @@
point_reward++
if(point_reward > 0)
open_machine()
- SendBack(H)
- playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
+ send_back(H)
+ playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE)
points += point_reward
credits += point_reward
- return "Experiment successful! [point_reward] new data-points collected."
+ return "Experiment successful! [point_reward] new data-points collected."
else
- playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 1)
- return "Experiment failed! No replacement organ detected."
+ playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
+ return "Experiment failed! No replacement organ detected."
else
say("Brain activity nonexistent - disposing sample...")
open_machine()
- SendBack(H)
- return "Specimen braindead - disposed."
+ send_back(H)
+ return "Specimen braindead - disposed."
-
-/obj/machinery/abductor/experiment/proc/SendBack(mob/living/carbon/human/H)
+/**
+ * send_back: Sends a mob back to a selected teleport location if safe
+ *
+ * Arguments:
+ * * H The human mob to be sent back
+ */
+/obj/machinery/abductor/experiment/proc/send_back(mob/living/carbon/human/H)
H.Sleeping(160)
H.uncuff()
if(console && console.pad && console.pad.teleport_target)
@@ -221,7 +180,6 @@
SSjob.SendToLateJoin(H, FALSE)
return
-
/obj/machinery/abductor/experiment/update_icon_state()
if(state_open)
icon_state = "experiment-open"
diff --git a/code/modules/antagonists/blob/blob.dm b/code/modules/antagonists/blob/blob.dm
index 1b076c9302..9a82bb546b 100644
--- a/code/modules/antagonists/blob/blob.dm
+++ b/code/modules/antagonists/blob/blob.dm
@@ -2,6 +2,7 @@
name = "Blob"
roundend_category = "blobs"
antagpanel_category = "Blob"
+ show_to_ghosts = TRUE
job_rank = ROLE_BLOB
threat = 20
var/datum/action/innate/blobpop/pop_action
diff --git a/code/modules/antagonists/blob/blob/blobs/shield.dm b/code/modules/antagonists/blob/blob/blobs/shield.dm
index a2a6ce94d3..a3a1403e58 100644
--- a/code/modules/antagonists/blob/blob/blobs/shield.dm
+++ b/code/modules/antagonists/blob/blob/blobs/shield.dm
@@ -45,11 +45,8 @@
desc = "A solid wall of slightly twitching tendrils with a reflective glow."
damaged_desc = "A wall of twitching tendrils with a reflective glow."
icon_state = "blob_glow"
+ flags_ricochet = RICOCHET_SHINY
point_return = 8
max_integrity = 100
brute_resist = 1
explosion_block = 2
-
-/obj/structure/blob/shield/reflective/check_projectile_ricochet(obj/item/projectile/P)
- return PROJECTILE_RICOCHET_FORCE
-
diff --git a/code/modules/antagonists/blob/blob/blobstrains/blazing_oil.dm b/code/modules/antagonists/blob/blob/blobstrains/blazing_oil.dm
index 97b974e28f..f97e271e72 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/blazing_oil.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/blazing_oil.dm
@@ -36,6 +36,6 @@
M.adjust_fire_stacks(round(reac_volume/10))
M.IgniteMob()
if(M)
- M.apply_damage(0.8*reac_volume, BURN)
+ M.apply_damage(0.8*reac_volume, BURN, wound_bonus=CANT_WOUND)
if(iscarbon(M))
M.emote("scream")
diff --git a/code/modules/antagonists/blob/blob/blobstrains/cryogenic_poison.dm b/code/modules/antagonists/blob/blob/blobstrains/cryogenic_poison.dm
index 9b8edcd0e5..f8ef269986 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/cryogenic_poison.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/cryogenic_poison.dm
@@ -22,7 +22,7 @@
M.reagents.add_reagent("frostoil", 0.3*reac_volume)
M.reagents.add_reagent("ice", 0.3*reac_volume)
M.reagents.add_reagent("cryogenic_poison", 0.3*reac_volume)
- M.apply_damage(0.2*reac_volume, BRUTE)
+ M.apply_damage(0.2*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
/datum/reagent/blob/cryogenic_poison/on_mob_life(mob/living/carbon/M)
M.adjustBruteLoss(0.3*REAGENTS_EFFECT_MULTIPLIER, 0)
diff --git a/code/modules/antagonists/blob/blob/blobstrains/electromagnetic_web.dm b/code/modules/antagonists/blob/blob/blobstrains/electromagnetic_web.dm
index 0e665603c9..45bf2b1e1d 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/electromagnetic_web.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/electromagnetic_web.dm
@@ -30,4 +30,4 @@
if(prob(reac_volume*2))
M.emp_act(EMP_LIGHT)
if(M)
- M.apply_damage(reac_volume, BURN)
+ M.apply_damage(reac_volume, BURN, wound_bonus=CANT_WOUND)
diff --git a/code/modules/antagonists/blob/blob/blobstrains/explosive_lattice.dm b/code/modules/antagonists/blob/blob/blobstrains/explosive_lattice.dm
index f8fd2e2f0d..3d005ba913 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/explosive_lattice.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/explosive_lattice.dm
@@ -33,8 +33,8 @@
if(ROLE_BLOB in L.faction) //no friendly fire
continue
var/aoe_volume = ..(L, TOUCH, initial_volume, 0, L.get_permeability_protection(), O)
- L.apply_damage(0.4*aoe_volume, BRUTE)
+ L.apply_damage(0.4*aoe_volume, BRUTE, wound_bonus=CANT_WOUND)
if(M)
- M.apply_damage(0.6*reac_volume, BRUTE)
+ M.apply_damage(0.6*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
else
- M.apply_damage(0.6*reac_volume, BRUTE)
+ M.apply_damage(0.6*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
diff --git a/code/modules/antagonists/blob/blob/blobstrains/networked_fibers.dm b/code/modules/antagonists/blob/blob/blobstrains/networked_fibers.dm
index fac3470c7a..8ccf2b9c99 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/networked_fibers.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/networked_fibers.dm
@@ -33,6 +33,6 @@
/datum/reagent/blob/networked_fibers/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
- M.apply_damage(0.6*reac_volume, BRUTE)
+ M.apply_damage(0.6*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
if(M)
- M.apply_damage(0.6*reac_volume, BURN)
+ M.apply_damage(0.6*reac_volume, BURN, wound_bonus=CANT_WOUND)
diff --git a/code/modules/antagonists/blob/blob/blobstrains/pressurized_slime.dm b/code/modules/antagonists/blob/blob/blobstrains/pressurized_slime.dm
index 6a984e66a2..11477712e7 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/pressurized_slime.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/pressurized_slime.dm
@@ -44,7 +44,7 @@
T.MakeSlippery(TURF_WET_LUBE, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS)
M.adjust_fire_stacks(-(reac_volume / 10))
M.ExtinguishMob()
- M.apply_damage(0.4*reac_volume, BRUTE)
+ M.apply_damage(0.4*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
if(M)
M.apply_damage(0.4*reac_volume, OXY)
if(M)
diff --git a/code/modules/antagonists/blob/blob/blobstrains/replicating_foam.dm b/code/modules/antagonists/blob/blob/blobstrains/replicating_foam.dm
index 00743c671e..5565135c63 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/replicating_foam.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/replicating_foam.dm
@@ -31,4 +31,4 @@
/datum/reagent/blob/replicating_foam/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
- M.apply_damage(0.7*reac_volume, BRUTE)
+ M.apply_damage(0.7*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
diff --git a/code/modules/antagonists/blob/blob/blobstrains/shifting_fragments.dm b/code/modules/antagonists/blob/blob/blobstrains/shifting_fragments.dm
index dbb3d6fb9b..9265158e1b 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/shifting_fragments.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/shifting_fragments.dm
@@ -32,4 +32,4 @@
/datum/reagent/blob/shifting_fragments/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
- M.apply_damage(0.7*reac_volume, BRUTE)
+ M.apply_damage(0.7*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
diff --git a/code/modules/antagonists/blob/blob/blobstrains/synchronous_mesh.dm b/code/modules/antagonists/blob/blob/blobstrains/synchronous_mesh.dm
index d58fb5b37d..daad0068e2 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/synchronous_mesh.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/synchronous_mesh.dm
@@ -30,9 +30,9 @@
/datum/reagent/blob/synchronous_mesh/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
- M.apply_damage(0.2*reac_volume, BRUTE)
+ M.apply_damage(0.2*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
if(M && reac_volume)
for(var/obj/structure/blob/B in range(1, M)) //if the target is completely surrounded, this is 2.4*reac_volume bonus damage, total of 2.6*reac_volume
if(M)
B.blob_attack_animation(M) //show them they're getting a bad time
- M.apply_damage(0.3*reac_volume, BRUTE)
+ M.apply_damage(0.3*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
diff --git a/code/modules/antagonists/blob/blob/theblob.dm b/code/modules/antagonists/blob/blob/theblob.dm
index 6a73dc579b..ed85726a4a 100644
--- a/code/modules/antagonists/blob/blob/theblob.dm
+++ b/code/modules/antagonists/blob/blob/theblob.dm
@@ -225,7 +225,7 @@
/obj/structure/blob/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/analyzer))
- user.changeNext_move(CLICK_CD_MELEE)
+ user.DelayNextAction(CLICK_CD_MELEE)
to_chat(user, "The analyzer beeps once, then reports: ")
SEND_SOUND(user, sound('sound/machines/ping.ogg'))
if(overmind)
diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm
index 47a98623db..a81f702a75 100644
--- a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm
+++ b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm
@@ -10,26 +10,24 @@
//
// Show as dead when...
-/datum/antagonist/bloodsucker/proc/LifeTick()// Should probably run from life.dm, same as handle_changeling, but will be an utter pain to move
- set waitfor = FALSE // Don't make on_gain() wait for this function to finish. This lets this code run on the side.
- var/notice_healing
- while(owner && !AmFinalDeath()) // owner.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) == src
- if(owner.current.stat == CONSCIOUS && !poweron_feed && !HAS_TRAIT(owner.current, TRAIT_FAKEDEATH)) // Deduct Blood
- AddBloodVolume(passive_blood_drain) // -.1 currently
- if(HandleHealing(1)) // Heal
- if(!notice_healing && owner.current.blood_volume > 0)
- to_chat(owner, "The power of your blood begins knitting your wounds...")
- notice_healing = TRUE
- else if(notice_healing == TRUE)
- notice_healing = FALSE // Apply Low Blood Effects
- HandleStarving() // Death
- HandleDeath() // Standard Update
- update_hud()// Daytime Sleep in Coffin
- if(SSticker.mode.is_daylight() && !HAS_TRAIT_FROM(owner.current, TRAIT_FAKEDEATH, "bloodsucker"))
- if(istype(owner.current.loc, /obj/structure/closet/crate/coffin))
- Torpor_Begin()
- // Wait before next pass
- sleep(10)
+/datum/antagonist/bloodsucker/proc/LifeTick() //Runs from BiologicalLife, handles all the bloodsucker constant proccesses
+ if(!owner || AmFinalDeath())
+ return
+ if(owner.current.stat == CONSCIOUS && !poweron_feed && !HAS_TRAIT(owner.current, TRAIT_FAKEDEATH)) // Deduct Blood
+ AddBloodVolume(passive_blood_drain) // -.1 currently
+ if(HandleHealing(1)) // Heal
+ if(!notice_healing && owner.current.blood_volume > 0)
+ to_chat(owner, "The power of your blood begins knitting your wounds...")
+ notice_healing = TRUE
+ else if(notice_healing)
+ notice_healing = FALSE // Apply Low Blood Effects
+ HandleStarving() // Death
+ HandleDeath() // Standard Update
+ update_hud()// Daytime Sleep in Coffin
+ if(SSticker.mode.is_daylight() && !HAS_TRAIT_FROM(owner.current, TRAIT_FAKEDEATH, "bloodsucker"))
+ if(istype(owner.current.loc, /obj/structure/closet/crate/coffin))
+ Torpor_Begin()
+ // Wait before next pass
FreeAllVassals() // Free my Vassals! (if I haven't yet)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -272,13 +270,15 @@
/datum/antagonist/bloodsucker/AmFinalDeath()
return owner && owner.AmFinalDeath()
-/datum/antagonist/changeling/AmFinalDeath()
- return owner && owner.AmFinalDeath()
/datum/mind/proc/AmFinalDeath()
return !current || QDELETED(current) || !isliving(current) || isbrain(current) || !get_turf(current) // NOTE: "isliving()" is not the same as STAT == CONSCIOUS. This is to make sure you're not a BORG (aka silicon)
/datum/antagonist/bloodsucker/proc/FinalDeath()
+ //Dont bother if we are already supposed to be dead
+ if(FinalDeath)
+ return
+ FinalDeath = TRUE //We are now supposed to die. Lets not spam it.
if(!iscarbon(owner.current)) //Check for non carbons.
owner.current.gib()
return
diff --git a/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm b/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm
index 888db8193b..1fcffff810 100644
--- a/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm
+++ b/code/modules/antagonists/bloodsucker/datum_bloodsucker.dm
@@ -37,6 +37,8 @@
var/had_toxlover
var/level_bloodcost
var/passive_blood_drain = -0.1 //The amount of blood we loose each bloodsucker life() tick
+ var/notice_healing //Var to see if you are healing for preventing spam of the chat message inform the user of such
+ var/FinalDeath //Have we reached final death? Used to prevent spam.
// LISTS
var/static/list/defaultTraits = list (TRAIT_STABLEHEART, TRAIT_NOBREATH, TRAIT_SLEEPIMMUNE, TRAIT_NOCRITDAMAGE, TRAIT_RESISTCOLD, TRAIT_RADIMMUNE, TRAIT_NIGHT_VISION, \
TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_AGEUSIA, TRAIT_COLDBLOODED, TRAIT_NONATURALHEAL, TRAIT_NOMARROW, TRAIT_NOPULSE, TRAIT_VIRUSIMMUNE, TRAIT_NODECAP, TRAIT_NOGUT)
@@ -50,7 +52,6 @@
AssignStarterPowersAndStats()// Give Powers & Stats
forge_bloodsucker_objectives()// Objectives & Team
update_bloodsucker_icons_added(owner.current, "bloodsucker") // Add Antag HUD
- LifeTick() // Run Life Function
. = ..()
@@ -683,6 +684,8 @@
owner.current.hud_used.sunlight_display.invisibility = INVISIBILITY_ABSTRACT
/datum/antagonist/bloodsucker/proc/update_hud(updateRank=FALSE)
+ if(FinalDeath)
+ return
// No Hud? Get out.
if(!owner.current.hud_used)
return
diff --git a/code/modules/antagonists/bloodsucker/objects/bloodsucker_coffin.dm b/code/modules/antagonists/bloodsucker/objects/bloodsucker_coffin.dm
index e85d3af5a0..a555677719 100644
--- a/code/modules/antagonists/bloodsucker/objects/bloodsucker_coffin.dm
+++ b/code/modules/antagonists/bloodsucker/objects/bloodsucker_coffin.dm
@@ -110,7 +110,7 @@
if (bloodsuckerdatum && bloodsuckerdatum.coffin == src)
bloodsuckerdatum.coffin = null
bloodsuckerdatum.lair = null
- to_chat(resident, "You sense that the link with your coffin, your sacred place of rest, has been brokem! You will need to seek another.")
+ to_chat(resident, "You sense that the link with your coffin, your sacred place of rest, has been broken! You will need to seek another.")
resident = null // Remove resident. Because this object isnt removed from the game immediately (GC?) we need to give them a way to see they don't have a home anymore.
/obj/structure/closet/crate/coffin/can_open(mob/living/user)
diff --git a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm
index 090ef45d89..536c07cd62 100644
--- a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm
+++ b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm
@@ -217,7 +217,7 @@
return FALSE
return ..()
-/obj/structure/bloodsucker/vassalrack/attack_hand(mob/user)
+/obj/structure/bloodsucker/vassalrack/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
//. = ..() // Taken from sacrificial altar in divine.dm
//if(.)
// return
@@ -361,9 +361,9 @@
torture_time -= I.force / 4
torture_dmg_brute += I.force / 4
//torture_dmg_burn += I.
- if(I.sharpness == IS_SHARP)
+ if(I.sharpness == SHARP_EDGED)
torture_time -= 1
- else if(I.sharpness == IS_SHARP_ACCURATE)
+ else if(I.sharpness == SHARP_POINTY)
torture_time -= 2
if(istype(I, /obj/item/weldingtool))
var/obj/item/weldingtool/welder = I
@@ -469,7 +469,7 @@
. += {"This is a magical candle which drains at the sanity of the fools who havent yet accepted your master, as long as it is active.\n
You can turn it on and off by clicking on it while you are next to it"} */
-/obj/structure/bloodsucker/candelabrum/attack_hand(mob/user)
+/obj/structure/bloodsucker/candelabrum/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
var/datum/antagonist/vassal/T = user.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
if(AmBloodsucker(user) || istype(T))
toggle()
diff --git a/code/modules/antagonists/bloodsucker/powers/feed.dm b/code/modules/antagonists/bloodsucker/powers/feed.dm
index a113f9c083..caca020b32 100644
--- a/code/modules/antagonists/bloodsucker/powers/feed.dm
+++ b/code/modules/antagonists/bloodsucker/powers/feed.dm
@@ -226,7 +226,9 @@
playsound(get_turf(target), 'sound/effects/splat.ogg', 40, 1)
if(ishuman(target))
var/mob/living/carbon/human/H = target
- H.bleed_rate += 5
+ var/obj/item/bodypart/head_part = H.get_bodypart(BODY_ZONE_HEAD)
+ if(head_part)
+ head_part.generic_bleedstacks += 5
target.add_splatter_floor(get_turf(target))
user.add_mob_blood(target) // Put target's blood on us. The donor goes in the ( )
target.add_mob_blood(target)
diff --git a/code/modules/antagonists/bloodsucker/powers/mesmerize.dm b/code/modules/antagonists/bloodsucker/powers/mesmerize.dm
index fccef4fb0c..efdd312c6e 100644
--- a/code/modules/antagonists/bloodsucker/powers/mesmerize.dm
+++ b/code/modules/antagonists/bloodsucker/powers/mesmerize.dm
@@ -128,11 +128,9 @@
target.face_atom(L)
target.Stun(power_time)
to_chat(L, "[target] is fixed in place by your hypnotic gaze.")
- target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // target.changeNext_move(power_time) // check click.dm
- target.mob_transforming = TRUE // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze.
+ target.DelayNextAction(power_time)
spawn(power_time)
if(istype(target) && success)
- target.mob_transforming = FALSE
if(istype(L) && target.stat == CONSCIOUS && (target in L.fov_view(10))) // They Woke Up! (Notice if within view)
to_chat(L, "[target] has snapped out of their trance.")
diff --git a/code/modules/antagonists/bloodsucker/powers/recuperate.dm b/code/modules/antagonists/bloodsucker/powers/recuperate.dm
index 6b8795ea02..90a2e3ff38 100644
--- a/code/modules/antagonists/bloodsucker/powers/recuperate.dm
+++ b/code/modules/antagonists/bloodsucker/powers/recuperate.dm
@@ -27,8 +27,9 @@
C.blood_volume -= 0.2
C.adjustStaminaLoss(-15)
// Stop Bleeding
- if(istype(H) && H.bleed_rate > 0 && rand(20) == 0)
- H.bleed_rate --
+ if(istype(H) && H.is_bleeding() && rand(20) == 0)
+ for(var/obj/item/bodypart/part in H.bodyparts)
+ part.generic_bleedstacks --
C.Jitter(5)
sleep(10)
// DONE!
diff --git a/code/modules/antagonists/bloodsucker/powers/trespass.dm b/code/modules/antagonists/bloodsucker/powers/trespass.dm
index cad3eeb88b..56b72a562e 100644
--- a/code/modules/antagonists/bloodsucker/powers/trespass.dm
+++ b/code/modules/antagonists/bloodsucker/powers/trespass.dm
@@ -81,9 +81,7 @@
var/mist_delay = max(5, 20 - level_current * 2.5) // Level up and do this faster.
// Freeze Me
- user.next_move = world.time + mist_delay
user.Stun(mist_delay, ignore_canstun = TRUE)
- user.mob_transforming = TRUE
user.density = FALSE
var/invis_was = user.invisibility
user.invisibility = INVISIBILITY_MAXIMUM
@@ -96,7 +94,6 @@
// Move & Freeze
if(isturf(target_turf))
do_teleport(owner, target_turf, no_effects=TRUE, channel = TELEPORT_CHANNEL_QUANTUM) // in teleport.dm?
- user.next_move = world.time + mist_delay / 2
user.Stun(mist_delay / 2, ignore_canstun = TRUE)
// Wait...
@@ -104,9 +101,7 @@
// Un-Hide & Freeze
user.dir = get_dir(my_turf, target_turf)
- user.next_move = world.time + mist_delay / 2
user.Stun(mist_delay / 2, ignore_canstun = TRUE)
- user.mob_transforming = FALSE
user.density = 1
user.invisibility = invis_was
diff --git a/code/modules/antagonists/changeling/cellular_emporium.dm b/code/modules/antagonists/changeling/cellular_emporium.dm
index b2c1a52a4a..2538394db7 100644
--- a/code/modules/antagonists/changeling/cellular_emporium.dm
+++ b/code/modules/antagonists/changeling/cellular_emporium.dm
@@ -13,10 +13,13 @@
changeling = null
. = ..()
-/datum/cellular_emporium/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/cellular_emporium/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/cellular_emporium/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "cellular_emporium", name, 900, 480, master_ui, state)
+ ui = new(user, src, "CellularEmporium", name)
ui.open()
/datum/cellular_emporium/ui_data(mob/user)
diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm
index 3267f2bdc1..d06ebe9d9d 100644
--- a/code/modules/antagonists/changeling/changeling.dm
+++ b/code/modules/antagonists/changeling/changeling.dm
@@ -20,6 +20,8 @@
var/datum/changelingprofile/first_prof = null
var/dna_max = 6 //How many extra DNA strands the changeling can store for transformation.
var/absorbedcount = 0
+ /// did we get succed by another changeling
+ var/hostile_absorbed = FALSE
var/trueabsorbs = 0//dna gained using absorb, not dna sting
var/chem_charges = 20
var/chem_storage = 75
diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm
index 3e2ff6f3dd..208fefee70 100644
--- a/code/modules/antagonists/changeling/powers/absorb.dm
+++ b/code/modules/antagonists/changeling/powers/absorb.dm
@@ -92,7 +92,7 @@
var/datum/antagonist/changeling/target_ling = target.mind.has_antag_datum(/datum/antagonist/changeling)
- if(target_ling)//If the target was a changeling, suck out their extra juice and objective points!
+ if(target_ling && !target_ling.hostile_absorbed)//If the target was a changeling, suck out their extra juice and objective points!
to_chat(user, "[target] was one of us. We have absorbed their power.")
target_ling.remove_changeling_powers()
changeling.geneticpoints += round(target_ling.geneticpoints/2)
@@ -102,6 +102,7 @@
changeling.chem_storage += round(target_ling.chem_storage/2)
changeling.chem_charges += min(target_ling.chem_charges, changeling.chem_storage)
target_ling.chem_charges = 0
+ target_ling.hostile_absorbed = TRUE
target_ling.chem_storage = 0
changeling.absorbedcount += (target_ling.absorbedcount)
target_ling.stored_profiles.len = 1
diff --git a/code/modules/antagonists/changeling/powers/fleshmend.dm b/code/modules/antagonists/changeling/powers/fleshmend.dm
index afef2a10c7..0299abb09a 100644
--- a/code/modules/antagonists/changeling/powers/fleshmend.dm
+++ b/code/modules/antagonists/changeling/powers/fleshmend.dm
@@ -1,6 +1,6 @@
/obj/effect/proc_holder/changeling/fleshmend
name = "Fleshmend"
- desc = "Our flesh rapidly regenerates, healing our burns, bruises, and shortness of breath. Functions while unconscious. This ability is loud, and might cause our blood to react violently to heat."
+ desc = "Our flesh rapidly regenerates, healing our burns, bruises, and shortness of breath, as well as hiding all of our scars. Costs 20 chemicals."
helptext = "If we are on fire, the healing effect will not function. Does not regrow limbs or restore lost blood."
chemical_cost = 20
loudness = 2
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index e4417a6d64..24288be078 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -164,7 +164,9 @@
armour_penetration = 20
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
+ wound_bonus = -60
+ bare_wound_bonus = 20
var/can_drop = FALSE
var/fake = FALSE
total_mass = TOTAL_MASS_HAND_REPLACEMENT
diff --git a/code/modules/antagonists/changeling/powers/regenerate.dm b/code/modules/antagonists/changeling/powers/regenerate.dm
index 1b27fa9694..a88422e7eb 100644
--- a/code/modules/antagonists/changeling/powers/regenerate.dm
+++ b/code/modules/antagonists/changeling/powers/regenerate.dm
@@ -29,6 +29,9 @@
C.emote("scream")
C.regenerate_limbs(1)
C.regenerate_organs()
+ for(var/i in C.all_wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.remove_wound()
if(!user.getorganslot(ORGAN_SLOT_BRAIN))
var/obj/item/organ/brain/B
if(C.has_dna() && C.dna.species.mutant_brain)
diff --git a/code/modules/antagonists/changeling/powers/revive.dm b/code/modules/antagonists/changeling/powers/revive.dm
index 6c2220648d..f193fb6736 100644
--- a/code/modules/antagonists/changeling/powers/revive.dm
+++ b/code/modules/antagonists/changeling/powers/revive.dm
@@ -36,9 +36,10 @@
. = ..()
if(!.)
return
-
- if(HAS_TRAIT(user, CHANGELING_DRAIN) || ((user.stat != DEAD) && !(HAS_TRAIT(user, TRAIT_DEATHCOMA))))
- var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
+ var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
+ if(!changeling)
+ return FALSE
+ if(changeling.hostile_absorbed || ((user.stat != DEAD) && !(HAS_TRAIT(user, TRAIT_DEATHCOMA))))
changeling.purchasedpowers -= src
return FALSE
diff --git a/code/modules/antagonists/changeling/powers/transform.dm b/code/modules/antagonists/changeling/powers/transform.dm
index 795ba772d6..8e3a36740b 100644
--- a/code/modules/antagonists/changeling/powers/transform.dm
+++ b/code/modules/antagonists/changeling/powers/transform.dm
@@ -17,8 +17,7 @@
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/clothing/glasses/changeling/attack_hand(mob/user)
+/obj/item/clothing/glasses/changeling/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(user, "You reabsorb [src] into your body.")
qdel(src)
@@ -33,8 +32,7 @@
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/clothing/under/changeling/attack_hand(mob/user)
+/obj/item/clothing/under/changeling/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(user, "You reabsorb [src] into your body.")
qdel(src)
@@ -50,8 +48,7 @@
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/clothing/suit/changeling/attack_hand(mob/user)
+/obj/item/clothing/suit/changeling/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(user, "You reabsorb [src] into your body.")
qdel(src)
@@ -65,8 +62,7 @@
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/clothing/head/changeling/attack_hand(mob/user)
+/obj/item/clothing/head/changeling/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(user, "You reabsorb [src] into your body.")
qdel(src)
@@ -81,8 +77,7 @@
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/clothing/shoes/changeling/attack_hand(mob/user)
+/obj/item/clothing/shoes/changeling/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(user, "You reabsorb [src] into your body.")
qdel(src)
@@ -97,8 +92,7 @@
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/clothing/gloves/changeling/attack_hand(mob/user)
+/obj/item/clothing/gloves/changeling/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(user, "You reabsorb [src] into your body.")
qdel(src)
@@ -113,8 +107,7 @@
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/clothing/mask/changeling/attack_hand(mob/user)
+/obj/item/clothing/mask/changeling/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(user, "You reabsorb [src] into your body.")
qdel(src)
@@ -131,8 +124,7 @@
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/changeling/attack_hand(mob/user)
+/obj/item/changeling/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(user, "You reabsorb [src] into your body.")
qdel(src)
diff --git a/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm b/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm
index 21d0035ef1..3ea4668df8 100644
--- a/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm
@@ -36,8 +36,7 @@
return
. = ..()
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/effect/clockwork/city_of_cogs_rift/attack_hand(atom/movable/AM)
+/obj/effect/clockwork/city_of_cogs_rift/on_attack_hand(atom/movable/AM)
beckon(AM)
/obj/effect/clockwork/city_of_cogs_rift/Bumped(atom/movable/AM)
diff --git a/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm b/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm
index 036ea37ada..454870d1e1 100644
--- a/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm
@@ -27,8 +27,7 @@
/obj/effect/clockwork/sigil/attack_tk(mob/user)
return //you can't tk stomp sigils, but you can hit them with something
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/effect/clockwork/sigil/attack_hand(mob/user)
+/obj/effect/clockwork/sigil/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
if(iscarbon(user) && !user.stat)
if(is_servant_of_ratvar(user) && user.a_intent != INTENT_HARM)
return ..()
@@ -217,6 +216,20 @@
else if(get_clockwork_power())
to_chat(L, "You feel a slight, static shock.")
+/obj/effect/clockwork/sigil/transmission/process()
+ var/power_drained = 0
+ var/power_mod = 0.005
+ for(var/t in spiral_range_turfs(SIGIL_ACCESS_RANGE, src))
+ var/turf/T = t
+ for(var/M in T)
+ var/atom/movable/A = M
+ power_drained += A.power_drain(TRUE)
+
+ CHECK_TICK
+
+ adjust_clockwork_power(power_drained * power_mod * 15)
+ new /obj/effect/temp_visual/ratvar/sigil/transmission(loc, 1 + (power_drained * 0.0035))
+
/obj/effect/clockwork/sigil/transmission/proc/charge_cyborg(mob/living/silicon/robot/cyborg)
if(!cyborg_checks(cyborg))
return
@@ -392,3 +405,49 @@
animation_number = initial(animation_number)
sigil_active = FALSE
animate(src, alpha = initial(alpha), time = 10, flags = ANIMATION_END_NOW)
+
+/obj/effect/clockwork/sigil/rite
+ name = "radiant sigil"
+ desc = "A glowing sigil glowing with barely-contained power."
+ clockwork_desc = "A sigil that will allow you to perform certain rites on it, provided you have access to sufficient power and materials."
+ icon_state = "sigiltransmission" //am big lazy - recolored transmission sigil
+ sigil_name = "Sigil of Rites"
+ alpha = 255
+ var/performing_rite = FALSE
+ color = "#ffe63a"
+ light_color = "#ffe63a"
+ light_range = 1
+ light_power = 2
+
+/obj/effect/clockwork/sigil/rite/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
+ . = ..()
+ if(.)
+ return
+ if(!is_servant_of_ratvar(user))
+ return
+ if(!GLOB.all_clockwork_rites.len) //Did we already generate the list?
+ generate_all_rites()
+ if(performing_rite)
+ to_chat(user, "Someone is already performing a rite here!")
+ return
+ var/list/possible_rites = list()
+ for(var/datum/clockwork_rite/R in GLOB.all_clockwork_rites)
+ possible_rites[R] = R
+ var/input_key = input(user, "Choose a rite", "Choosing a rite") as null|anything in possible_rites
+ if(!input_key)
+ return
+ var/datum/clockwork_rite/CR = possible_rites[input_key]
+ if(!CR)
+ return
+ var/choice = alert(user, "What to do with this rite?", "What to do?", "Cast", "Show Info", "Cancel")
+ switch(choice)
+ if("Cast")
+ CR.try_cast(src, user)
+ if("Show Info")
+ var/infotext = CR.build_info()
+ to_chat(user, infotext)
+
+/obj/effect/clockwork/sigil/rite/proc/generate_all_rites() //The first time someone uses a sigil of rites, all the rites are actually generated. No need to have a bunch of random datums laying around all the time.
+ for(var/V in subtypesof(/datum/clockwork_rite))
+ var/datum/clockwork_rite/R = new V
+ GLOB.all_clockwork_rites += R
diff --git a/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm b/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm
index 36aaa27716..00c52e4a59 100644
--- a/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm
@@ -14,6 +14,8 @@
var/uses = 1 //How many objects or mobs can go through the portal
var/obj/effect/clockwork/spatial_gateway/linked_gateway //The gateway linked to this one
var/timerid
+ var/is_stable = FALSE
+ var/busy = FALSE //If someone is already working on closing the gateway, only needed for stable gateways but in the parent to not need typecasting
/obj/effect/clockwork/spatial_gateway/Initialize()
. = ..()
@@ -31,11 +33,16 @@
clockwork_desc = "A gateway in reality. It can both send and receive objects."
else
clockwork_desc = "A gateway in reality. It can only [sender ? "send" : "receive"] objects."
- timerid = QDEL_IN(src, lifetime)
+ if(is_stable)
+ return
+ timerid = QDEL_IN(src, lifetime) //We only need this if the gateway is not stable
//set up a gateway with another gateway
/obj/effect/clockwork/spatial_gateway/proc/setup_gateway(obj/effect/clockwork/spatial_gateway/gatewayB, set_duration, set_uses, two_way)
- if(!gatewayB || !set_duration || !uses)
+ if(!gatewayB)
+ return FALSE
+
+ if((!set_duration || !uses) && !is_stable)
return FALSE
linked_gateway = gatewayB
gatewayB.linked_gateway = src
@@ -55,7 +62,7 @@
/obj/effect/clockwork/spatial_gateway/examine(mob/user)
. = ..()
if(is_servant_of_ratvar(user) || isobserver(user))
- . += "It has [uses] use\s remaining."
+ . += " [is_stable ? "It is stabilised and can be used as much as is neccessary." : "It has [uses] use\s remaining."]"
//ATTACK GHOST IGNORING PARENT RETURN VALUE
/obj/effect/clockwork/spatial_gateway/attack_ghost(mob/user)
@@ -63,8 +70,7 @@
user.forceMove(get_turf(linked_gateway))
..()
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/effect/clockwork/spatial_gateway/attack_hand(mob/living/user)
+/obj/effect/clockwork/spatial_gateway/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
if(!uses)
return FALSE
if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling))
@@ -122,9 +128,9 @@
/obj/effect/clockwork/spatial_gateway/Bumped(atom/movable/AM)
..()
if(!QDELETED(AM))
- pass_through_gateway(AM, FALSE)
+ pass_through_gateway(AM)
-/obj/effect/clockwork/spatial_gateway/proc/pass_through_gateway(atom/movable/A, no_cost)
+/obj/effect/clockwork/spatial_gateway/proc/pass_through_gateway(atom/movable/A, no_cost = FALSE)
if(!linked_gateway)
qdel(src)
return FALSE
@@ -198,6 +204,10 @@
return procure_gateway(invoker, time_duration, gateway_uses, two_way)
var/istargetobelisk = istype(target, /obj/structure/destructible/clockwork/powered/clockwork_obelisk)
var/issrcobelisk = istype(src, /obj/structure/destructible/clockwork/powered/clockwork_obelisk)
+ if(!issrcobelisk && target.z != invoker.z && (is_reebe(invoker.z) || is_reebe(target.z)) && !GLOB.ratvar_awakens) //You need obilisks to get from and to reebe. Costs alot of power, unless you use stable gateways.
+ to_chat(invoker, "The distance between reebe and the mortal realm is far too vast to bridge with a gateway your slab can create, my child. \
+ Use an obilisk instead!")
+ return procure_gateway(invoker, time_duration, gateway_uses, two_way)
if(issrcobelisk)
if(!anchored)
to_chat(invoker, "[src] is no longer secured!")
@@ -218,12 +228,63 @@
gateway_uses = round(gateway_uses * (2 * efficiency), 1)
time_duration = round(time_duration * (2 * efficiency), 1)
CO.active = TRUE //you'd be active in a second but you should update immediately
- invoker.visible_message("The air in front of [invoker] ripples before suddenly tearing open!", \
- "With a word, you rip open a [two_way ? "two-way":"one-way"] rift to [input_target_key]. It will last for [DisplayTimeText(time_duration)] and has [gateway_uses] use[gateway_uses > 1 ? "s" : ""].")
- var/obj/effect/clockwork/spatial_gateway/S1 = new(issrcobelisk ? get_turf(src) : get_step(get_turf(invoker), invoker.dir))
- var/obj/effect/clockwork/spatial_gateway/S2 = new(istargetobelisk ? get_turf(target) : get_step(get_turf(target), target.dir))
+ if(issrcobelisk && istargetobelisk && src.z != target.z && (is_reebe(src.z) || is_reebe(target.z)))
+ invoker.visible_message("The air in front of [invoker] ripples before suddenly tearing open!", \
+ "With a word, you rip open a stable two-way rift between reebe and the mortal realm.")
+ var/obj/effect/clockwork/spatial_gateway/stable/stable_S1 = new(get_turf(src))
+ var/obj/effect/clockwork/spatial_gateway/stable/stable_S2 = new(get_turf(target))
+ stable_S1.setup_gateway(stable_S2)
+ stable_S2.visible_message("The air in front of [target] ripples before suddenly tearing open!")
+ else
+ invoker.visible_message("The air in front of [invoker] ripples before suddenly tearing open!", \
+ "With a word, you rip open a [two_way ? "two-way":"one-way"] rift to [input_target_key]. It will last for [DisplayTimeText(time_duration)] and has [gateway_uses] use[gateway_uses > 1 ? "s" : ""].")
+ var/obj/effect/clockwork/spatial_gateway/S1 = new(issrcobelisk ? get_turf(src) : get_step(get_turf(invoker), invoker.dir))
+ var/obj/effect/clockwork/spatial_gateway/S2 = new(istargetobelisk ? get_turf(target) : get_step(get_turf(target), target.dir))
- //Set up the portals now that they've spawned
- S1.setup_gateway(S2, time_duration, gateway_uses, two_way)
- S2.visible_message("The air in front of [target] ripples before suddenly tearing open!")
+ //Set up the portals now that they've spawned
+ S1.setup_gateway(S2, time_duration, gateway_uses, two_way)
+ S2.visible_message("The air in front of [target] ripples before suddenly tearing open!")
return TRUE
+
+//Stable Gateway: Used to travel to and from reebe without any further powercost. Needs a clockwork obilisk to keep active, but stays active as long as it is not deactivated via an null rod or a slab, or the obilisk is destroyed
+/obj/effect/clockwork/spatial_gateway/stable
+ name = "stable gateway"
+ is_stable = TRUE
+
+/obj/effect/clockwork/spatial_gateway/stable/ex_act(severity)
+ if(severity == 1)
+ start_shutdown() //Yes, you can chain devastation-level explosions to delay a gateway shutdown, if you somehow manage to do it without breaking the obelisk. Is it worth it? Probably not.
+ return TRUE
+ return FALSE
+
+/obj/effect/clockwork/spatial_gateway/stable/setup_gateway(obj/effect/clockwork/spatial_gateway/stable/gatewayB) //Reduced setup call due to some things being irrelevant for stable gateways
+ return ..(gatewayB, 1, 1, TRUE) //Uses and time irrelevant due to is_stable
+
+/obj/effect/clockwork/spatial_gateway/stable/attackby(obj/item/I, mob/living/user, params)
+ if(!istype(I, /obj/item/clockwork/slab) || !is_servant_of_ratvar(user) || busy)
+ return ..()
+ busy = TRUE
+ linked_gateway.busy = TRUE
+ user.visible_message("The rift begins to ripple as [user] points [user.p_their()] slab at it!", " You begin to shutdown the stabilised gateway with your slab.")
+ linked_gateway.visible_message("")
+ var/datum/beam/B = user.Beam(src, icon_state = "nzcrentrs_power", maxdistance = 50, time = 80) //Not too fancy, but this'll do.. for now.
+ if(do_after(user, 80, target = src)) //Eight seconds to initiate the closing, then another two before is closes.
+ to_chat(user, "You successfully set the gateway to shutdown in another two seconds.")
+ start_shutdown()
+ qdel(B)
+ busy = FALSE
+ linked_gateway.busy = FALSE
+ return TRUE
+
+/obj/effect/clockwork/spatial_gateway/stable/proc/start_shutdown()
+ deltimer(timerid)
+ deltimer(linked_gateway.timerid)
+ timerid = QDEL_IN(src, 20)
+ linked_gateway.timerid = QDEL_IN(linked_gateway, 20)
+ animate(src, alpha = 0, transform = matrix()*2, time = 20, flags = ANIMATION_END_NOW)
+ animate(linked_gateway, alpha = 0, transform = matrix()*2, time = 20, flags = ANIMATION_END_NOW)
+ src.visible_message("[src] begins to destabilise!")
+ linked_gateway.visible_message("[linked_gateway] begins to destabilise!")
+
+/obj/effect/clockwork/spatial_gateway/stable/pass_through_gateway(atom/movable/A, no_cost = TRUE)
+ return ..()
\ No newline at end of file
diff --git a/code/modules/antagonists/clockcult/clock_helpers/clock_rites.dm b/code/modules/antagonists/clockcult/clock_helpers/clock_rites.dm
new file mode 100644
index 0000000000..7dabb18f03
--- /dev/null
+++ b/code/modules/antagonists/clockcult/clock_helpers/clock_rites.dm
@@ -0,0 +1,196 @@
+//This file is for clock rites, mainly used by the Sigil of Rites in clock_sigils.dm
+//The rites themselves are in this file to prevent bloating the other file too much, aswell as for easier access
+
+#define INFINITE -1
+
+//The base clockwork rite. This should never be visible
+/datum/clockwork_rite
+ var/name = "Rite of THE frog" //The name of the rite
+ var/desc = "This rite is used to summon the legendary frog whose-name-shall-not-be-spoken, ender of many worlds." //What does this rite do? Shown to cultists if they choose 'Show Info' after selecting the rite.
+ var/list/required_ingredients = list(/obj/item/clockwork) //What does this rite require?
+ var/power_cost = 0 //How much power does this rite cost.. or does it even add power?
+ var/requires_human = FALSE //Does the rite require a ../carbon/human on the rune?
+ var/must_be_servant = TRUE //If the above is true, does the human need to be a servant?
+ var/target_can_be_invoker = TRUE //Does this rite work if the invoker is also the target?
+ var/cast_time = 0 //How long does the rite take to cast?
+ var/limit = INFINITE //How often can this rite be used per round? Set this to INFINITE for unlimited, 0 for disallowed, anything above 0 for a limit
+ var/times_used = 0 //How often has the rite already been used this shift?
+ var/rite_cast_sound = 'sound/items/bikehorn.ogg' //The sound played when successfully casting the rite. If it honks, the one adding the rite forgot to set one (or was just lazy).
+
+/datum/clockwork_rite/proc/try_cast(var/obj/effect/clockwork/sigil/rite/R, var/mob/living/invoker) //Performs a ton of checks to see if the invoker can cast the rite
+ if(!istype(R))
+ return FALSE
+ if(!R || !R.loc)
+ return FALSE
+ var/turf/T = R.loc
+ if(!T) //Uh oh something is fucky
+ return FALSE
+
+ if(limit != INFINITE && times_used >= limit) //Is the limit on casts exceeded?
+ to_chat(invoker, "There are no more uses left for this rite!")
+ return FALSE
+
+ var/mob/living/carbon/human/H //This is only used if requires_human is TRUE
+ if(requires_human) //In case this requires a target
+ for(var/mob/living/carbon/human/possible_H in T)
+ if((!must_be_servant || is_servant_of_ratvar(possible_H)) && (target_can_be_invoker || invoker != possible_H))
+ H = possible_H
+ break
+ if(!H)
+ to_chat(invoker, "There is no target for the rite on the sigil!")
+ return FALSE
+
+ if(required_ingredients.len) //In case this requires materials
+ var/is_missing_materials = FALSE
+ for(var/I in required_ingredients)
+ var/obj/item/Material = locate(I) in T
+ if(!Material)
+ is_missing_materials = TRUE
+ break
+ if(is_missing_materials)
+ var/still_required_string = ""
+ for(var/i = 1 to required_ingredients.len)
+ var/obj/O = required_ingredients[i]
+ if(i != 1)
+ still_required_string += ", "
+ still_required_string += "a [initial(O.name)]"
+ to_chat(invoker, "There are still materials missing for this rite. You require [still_required_string].")
+ return FALSE
+
+ if(power_cost) //If this costs power
+ if(!get_clockwork_power(power_cost))
+ to_chat(invoker, "There is not enough power for this rite!")
+ return FALSE
+ R.performing_rite = TRUE
+ if(!do_after(invoker, cast_time, target = R))
+ to_chat(invoker, "Your rite is disrupted.")
+ R.performing_rite = FALSE
+ return FALSE
+ . = cast(invoker, T, H)
+ if(!.)
+ to_chat(invoker, " You fail casting [name]")
+ post_cast(FALSE)
+ else
+ to_chat(invoker, "You successfully cast [name]")
+ post_cast(TRUE)
+ R.performing_rite = FALSE
+ return
+
+/datum/clockwork_rite/proc/cast(var/mob/living/invoker, var/turf/T, var/mob/living/carbon/human/target) //Casts the rite and uses up ingredients. Doublechecks some things to prevent bypassing some restrictions via funky timing or badminnery.
+ if(!T || !invoker)
+ return FALSE
+ if(requires_human && !target)
+ return FALSE
+ if(power_cost && !get_clockwork_power(power_cost))
+ return FALSE
+ adjust_clockwork_power(-power_cost)
+ if(limit != INFINITE && times_used >= limit)
+ return FALSE
+ if(required_ingredients.len)
+ var/is_missing_materials = FALSE
+ for(var/I in required_ingredients)
+ var/obj/item/Material = locate(I) in T
+ if(!Material)
+ is_missing_materials = TRUE
+ break
+ qdel(Material)
+ if(is_missing_materials)
+ return FALSE
+ playsound(T, rite_cast_sound, 50, 2)
+ return TRUE
+
+/datum/clockwork_rite/proc/post_cast(var/cast_succeeded)
+ if(cast_succeeded)
+ times_used++
+ return TRUE
+
+/datum/clockwork_rite/proc/build_info() //Constructs the info text of a given rite, based on the vars of the rite
+ . = ""
+ . += "This is the [name].\n"
+ . += "[desc]\n"
+ . += "It requires: "
+ if(required_ingredients.len)
+ var/material_string = ""
+ for(var/i = 1 to required_ingredients.len)
+ var/obj/O = required_ingredients[i]
+ if(i != 1)
+ material_string += ", "
+ material_string += "a [initial(O.name)]"
+ . += "[material_string].\n"
+ else
+ . += "no materials.\n"
+ . += "It [power_cost >= 0 ? "costs" : "generates"] [power_cost ? "[power_cost]" : "no"] power.\n"
+ . += "It requires [requires_human ? " a human" : " no"] target.\n"
+ if(requires_human)
+ . += "The target [must_be_servant ? "cannot be" : "can be"] a nonservant.\n"
+ . += "The target [target_can_be_invoker ? "can be" : "cannot be"] the invoker.\n"
+ . += "It requires [cast_time/10] seconds to cast.\n"
+ . += "It has been used [times_used] time[times_used != 1 ? "s" : ""], out of [limit != INFINITE ? "[limit]" : "infinite"] available uses."
+
+//Adds a organ or cybernetic implant to a servant without the need for surgery. Cannot be used with brains for.. reasons.
+/datum/clockwork_rite/advancement
+ name = "Rite of Advancement"
+ desc = "This rite is used to augment a servant with organs or cybernetic implants. The organ of choice, aswell as the servant and the required ingredients must be placed on the sigil for this rite to take place."
+ required_ingredients = list(/obj/item/assembly/prox_sensor, /obj/item/stock_parts/cell)
+ power_cost = 500
+ requires_human = TRUE
+ cast_time = 40
+ rite_cast_sound = 'sound/magic/blind.ogg'
+
+/datum/clockwork_rite/advancement/cast(var/mob/living/invoker, var/turf/T, var/mob/living/carbon/human/target)
+ var/obj/item/organ/O = locate(/obj/item/organ) in T
+ if(!O)
+ return FALSE
+ if(istype(O, /obj/item/organ/brain)) //NOPE
+ return FALSE
+ . = ..()
+ if(!.)
+ return FALSE
+ O.Insert(target)
+ new /obj/effect/temp_visual/ratvar/sigil/transgression(T)
+
+//Heals all wounds (not damage) on the target, causing toxloss proportional to amount of wounds healed. 10 damage per wound.
+/datum/clockwork_rite/treat_wounds
+ name = "Rite of Woundmending"
+ desc = "This rite is used to heal wounds of the servant on the rune. It causes toxins damage proportional to the amount of wounds healed. This can be lethal if performed on an critically injured target."
+ required_ingredients = list(/obj/item/stock_parts/cell, /obj/item/healthanalyzer, /obj/item/reagent_containers/food/drinks/bottle/holyoil)
+ power_cost = 300
+ requires_human = TRUE
+ must_be_servant = FALSE
+ target_can_be_invoker = FALSE
+ cast_time = 80
+ rite_cast_sound = 'sound/magic/staff_healing.ogg'
+
+/datum/clockwork_rite/treat_wounds/cast(var/mob/living/invoker, var/turf/T, var/mob/living/carbon/human/target)
+ if(!target)
+ return FALSE
+ if(!target.all_wounds.len)
+ to_chat(invoker, "This one does not require mending.")
+ return FALSE
+ .= ..()
+ if(!.)
+ return FALSE
+ target.adjustToxLoss(10 * target.all_wounds.len)
+ QDEL_LIST(target.all_wounds)
+ to_chat(target, "You feel your wounds heal, but are overcome with deep nausea.")
+ new /obj/effect/temp_visual/ratvar/sigil/vitality(T)
+
+//Summons a brass claw implant on the sigil, which can extend a claw that benefits from repeatedly attacking a single target. Can only be cast a limited amount of times.
+/datum/clockwork_rite/summon_claw
+ name = "Rite of the Claw"
+ desc = "Summons a special arm implant that, when added to a servant's limb, will allow them to extend and retract a claw at will. Don't leave any implants you want to keep on this rune when casting the rite."
+ required_ingredients = list(/obj/item/stock_parts/cell, /obj/item/organ/cyberimp, /obj/item/assembly/flash)
+ power_cost = 1000
+ cast_time = 60
+ limit = 4
+ rite_cast_sound = 'sound/magic/clockwork/fellowship_armory.ogg'
+
+/datum/clockwork_rite/summon_claw/cast(var/mob/living/invoker, var/turf/T, var/mob/living/carbon/human/target)
+ . = ..()
+ if(!.)
+ return FALSE
+ var/obj/item/organ/cyberimp/arm/clockwork/claw/CL = new /obj/item/organ/cyberimp/arm/clockwork/claw(T)
+ CL.visible_message("[CL] materialises out of thin air!")
+ new /obj/effect/temp_visual/ratvar/sigil/transmission(T,2)
+
+#undef INFINITE
diff --git a/code/modules/antagonists/clockcult/clock_helpers/scripture_checks.dm b/code/modules/antagonists/clockcult/clock_helpers/scripture_checks.dm
index e5497d7c9f..66e20b6e87 100644
--- a/code/modules/antagonists/clockcult/clock_helpers/scripture_checks.dm
+++ b/code/modules/antagonists/clockcult/clock_helpers/scripture_checks.dm
@@ -38,10 +38,11 @@
set_slab.update_quickbind()
/proc/generate_all_scripture()
- if(!GLOB.all_scripture.len)
- for(var/V in sortList(subtypesof(/datum/clockwork_scripture), /proc/cmp_clockscripture_priority))
- var/datum/clockwork_scripture/S = new V
- GLOB.all_scripture[S.type] = S
+ if(GLOB.all_scripture.len)
+ return
+ for(var/V in sortList(subtypesof(/datum/clockwork_scripture) - list(/datum/clockwork_scripture/channeled, /datum/clockwork_scripture/create_object, /datum/clockwork_scripture/create_object/construct), /proc/cmp_clockscripture_priority))
+ var/datum/clockwork_scripture/S = new V
+ GLOB.all_scripture[S.type] = S
//changes construction value
/proc/change_construction_value(amount)
diff --git a/code/modules/antagonists/clockcult/clock_items/clock_augments.dm b/code/modules/antagonists/clockcult/clock_items/clock_augments.dm
new file mode 100644
index 0000000000..2131aa7160
--- /dev/null
+++ b/code/modules/antagonists/clockcult/clock_items/clock_augments.dm
@@ -0,0 +1,32 @@
+//This file is for snowflakey clock augmentations and clock-themed cybernetic implants.
+
+//The base clockie arm implant, which only clock cultist can use unless it is emagged. THIS SHOULD NEVER ACTUALLY EXIST
+/obj/item/organ/cyberimp/arm/clockwork
+ name = "clock-themed arm-mounted implant"
+ var/clockwork_desc = "According to Ratvar, this really shouldn't exist. Tell Him about this immediately."
+ syndicate_implant = TRUE
+ icon_state = "clock_arm_implant"
+
+/obj/item/organ/cyberimp/arm/clockwork/ui_action_click()
+ if(is_servant_of_ratvar(owner) || (obj_flags & EMAGGED)) //If you somehow manage to steal a clockie's implant AND have an emag AND manage to get it implanted for yourself, good on ya!
+ return ..()
+ to_chat(owner, "The implant refuses to activate..")
+
+/obj/item/organ/cyberimp/arm/clockwork/examine(mob/user)
+ if((is_servant_of_ratvar(user) || isobserver(user)) && clockwork_desc)
+ desc = clockwork_desc
+ . = ..()
+ desc = initial(desc)
+
+/obj/item/organ/cyberimp/arm/clockwork/emag_act()
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ to_chat(usr, "You emag [src], hoping it'll achieve something..")
+
+//Brass claw implant. Holds the brass claw from brass_claw.dm and can extend / retract it at will.
+/obj/item/organ/cyberimp/arm/clockwork/claw
+ name = "brass claw implant"
+ desc = "Yikes, the claw attached to this looks pretty darn sharp."
+ clockwork_desc = "This implant, when added to a servant's arm, allows them to extend and retract a claw at will, though this is mildly painful to do. It will refuse to work for any non-servants."
+ contents = newlist(/obj/item/clockwork/brass_claw)
diff --git a/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm b/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm
index 40aca961fc..a6f2ee6d90 100644
--- a/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm
@@ -29,7 +29,7 @@
owner.visible_message("[owner]'s [weapon.name] flickers and disappears!")
to_chat(owner, "You dismiss [weapon].")
QDEL_NULL(weapon)
- weapon_reset(RATVARIAN_SPEAR_COOLDOWN * 0.5)
+ weapon_reset(RATVARIAN_WEAPON_COOLDOWN * 0.5)
return
else
weapon.visible_message("[weapon] suddenly flickers and disappears!")
diff --git a/code/modules/antagonists/clockcult/clock_items/clock_weapons/brass_claw.dm b/code/modules/antagonists/clockcult/clock_items/clock_weapons/brass_claw.dm
new file mode 100644
index 0000000000..340f01f6f8
--- /dev/null
+++ b/code/modules/antagonists/clockcult/clock_items/clock_weapons/brass_claw.dm
@@ -0,0 +1,51 @@
+//Brass claw, an armblade-like weapon used by a clock implant. Stealthy if retracted, very obvious if active.
+//Bit weaker than an armblade strength-wise but gains combo on consecutive attacks against the same target, which causes bonus damage
+
+/obj/item/clockwork/brass_claw
+ name = "brass claw"
+ desc = "A very sharp claw made out of brass."
+ clockwork_desc = "A incredibly sharp claw made out of brass. It is quite effective at crippling enemies, though very obvious when extended.\nGains combo on consecutive attacks against a target, causing bonus damage."
+ icon_state = "brass_claw" //Codersprite moment
+ item_state = "brass_claw"
+ lefthand_file = 'icons/mob/inhands/antag/clockwork_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/antag/clockwork_righthand.dmi'
+ w_class = WEIGHT_CLASS_HUGE
+ force = 15 //Doesn't generate vitality like the spear does / has somewhat less damage, but quite good at wounding and gets through armor pretty well. Also gains 2 bonus damage per consecutive attack on the same target
+ throwforce = 0 //haha yes lets be safe about this
+ throw_range = 0
+ throw_speed = 0
+ armour_penetration = 20
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+ sharpness = SHARP_EDGED
+ wound_bonus = 5
+ bare_wound_bonus = 15
+ total_mass = TOTAL_MASS_HAND_REPLACEMENT
+ var/mob/living/last_attacked
+ var/combo = 0
+ var/damage_per_combo = 2
+ var/maximum_combo_damage = 18 //33 damage on max stacks. Usually the target will already be dead by then but if they somehow aren't, better to have this capped
+
+/obj/item/clockwork/brass_claw/Initialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 60, 80)
+
+/obj/item/clockwork/brass_claw/examine(mob/user)
+ if(is_servant_of_ratvar(user))
+ clockwork_desc += "\nIt has [combo] combo stacks built up against the current target, causing [min(maximum_combo_damage, combo * damage_per_combo)] bonus damage."
+ . = ..()
+ clockwork_desc = initial(clockwork_desc)
+
+/obj/item/clockwork/brass_claw/attack(mob/living/target, mob/living/carbon/human/user)
+ . = ..()
+ if(QDELETED(target) || target.anti_magic_check(chargecost = 0) || is_servant_of_ratvar(target))
+ return
+ if(target != last_attacked) //Loses all combat on switching targets
+ last_attacked = target
+ combo = 0
+ else
+ if(!iscultist(target)) //Hostile cultists being hit stacks up combo far faster than usual
+ combo++
+ else
+ combo += 3
+ target.adjustBruteLoss(min(maximum_combo_damage, combo * damage_per_combo))
diff --git a/code/modules/antagonists/clockcult/clock_items/clock_weapons/ratvarian_spear.dm b/code/modules/antagonists/clockcult/clock_items/clock_weapons/ratvarian_spear.dm
index 234f0445e0..aa69478217 100644
--- a/code/modules/antagonists/clockcult/clock_items/clock_weapons/ratvarian_spear.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clock_weapons/ratvarian_spear.dm
@@ -8,10 +8,12 @@
force = 15 //Extra damage is dealt to targets in attack()
throwforce = 25
armour_penetration = 10
- sharpness = IS_SHARP_ACCURATE
+ sharpness = SHARP_POINTY
attack_verb = list("stabbed", "poked", "slashed")
hitsound = 'sound/weapons/bladeslice.ogg'
w_class = WEIGHT_CLASS_BULKY
+ block_parry_data = /datum/block_parry_data/ratvarian_spear
+ item_flags = ITEM_CAN_PARRY
var/bonus_burn = 5
/obj/item/clockwork/weapon/ratvarian_spear/ratvar_act()
@@ -43,7 +45,7 @@
else if(iscultist(target) || isconstruct(target))
to_chat(target, "Your body flares with agony at [src]'s presence!")
bonus_damage *= 3 //total 30 damage on cultists, 50 with ratvar
- GLOB.clockwork_vitality += target.adjustFireLoss(bonus_damage) //adds the damage done to existing vitality
+ GLOB.clockwork_vitality += max(0, target.adjustFireLoss(bonus_damage)) //adds the damage done to existing vitality
/obj/item/clockwork/weapon/ratvarian_spear/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
var/turf/T = get_turf(hit_atom)
@@ -78,5 +80,17 @@
if(T) //make sure we're not in null or something
T.visible_message("[src] [pick("cracks in two and fades away", "snaps in two and dematerializes")]!")
new /obj/effect/temp_visual/ratvar/spearbreak(T)
- action.weapon_reset(RATVARIAN_SPEAR_COOLDOWN)
+ action.weapon_reset(RATVARIAN_WEAPON_COOLDOWN)
+//A very short, very effective parry that counts on you predicting when the enemy will attack.
+/datum/block_parry_data/ratvarian_spear
+ parry_time_windup = 0 //Very good for predicting
+ parry_time_active = 3 //Very short
+ parry_time_spindown = 1
+ parry_time_perfect = 2
+ parry_efficiency_perfect = 110 //Very low leeway for counterattacks...
+ parry_efficiency_considered_successful = 0.8
+ parry_efficiency_to_counterattack = 1
+ parry_cooldown = 15 //But also very low cooldown..
+ parry_failed_stagger_duration = 2 SECONDS //And relatively small penalties for failing.
+ parry_failed_clickcd_duration = 1 SECONDS
diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm
index a4f8bf8062..d08caa39d7 100644
--- a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm
@@ -8,7 +8,7 @@
resistance_flags = FIRE_PROOF | ACID_PROOF
flags_inv = HIDEEARS|HIDEHAIR|HIDEFACE|HIDESNOUT
mutantrace_variation = STYLE_MUZZLE
- armor = list("melee" = 50, "bullet" = 70, "laser" = -25, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 50, "bullet" = 70, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100, "magic" = 60, "wound" = 65)
/obj/item/clothing/head/helmet/clockwork/Initialize()
. = ..()
@@ -21,17 +21,17 @@
/obj/item/clothing/head/helmet/clockwork/ratvar_act()
if(GLOB.ratvar_awakens)
- armor = getArmor(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100)
+ armor = getArmor(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100, magic = 100, wound = 100)
clothing_flags |= STOPSPRESSUREDAMAGE
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else if(GLOB.ratvar_approaches)
- armor = getArmor(melee = 70, bullet = 80, laser = -15, energy = 25, bomb = 70, bio = 0, rad = 0, fire = 100, acid = 100)
+ armor = getArmor(melee = 70, bullet = 80, laser = 10, energy = 25, bomb = 70, bio = 0, rad = 0, fire = 100, acid = 100,, magic = 70, wound = 75)
clothing_flags |= STOPSPRESSUREDAMAGE
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else
- armor = getArmor(melee = 60, bullet = 70, laser = -25, energy = 0, bomb = 60, bio = 0, rad = 0, fire = 100, acid = 100)
+ armor = getArmor(melee = 60, bullet = 70, laser = 0, energy = 0, bomb = 60, bio = 0, rad = 0, fire = 100, acid = 100, magic = 60, wound = 65)
clothing_flags &= ~STOPSPRESSUREDAMAGE
max_heat_protection_temperature = initial(max_heat_protection_temperature)
min_cold_protection_temperature = initial(min_cold_protection_temperature)
@@ -68,7 +68,7 @@
cold_protection = CHEST|GROIN|LEGS
heat_protection = CHEST|GROIN|LEGS
resistance_flags = FIRE_PROOF | ACID_PROOF
- armor = list("melee" = 60, "bullet" = 70, "laser" = -25, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 60, "bullet" = 70, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100, "magic" = 60, "wound" = 65)
allowed = list(/obj/item/clockwork, /obj/item/clothing/glasses/wraith_spectacles, /obj/item/clothing/glasses/judicial_visor, /obj/item/mmi/posibrain/soul_vessel, /obj/item/reagent_containers/food/drinks/bottle/holyoil)
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC
@@ -83,17 +83,17 @@
/obj/item/clothing/suit/armor/clockwork/ratvar_act()
if(GLOB.ratvar_awakens)
- armor = getArmor(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100)
+ armor = getArmor(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100, magic = 100, wound = 100)
clothing_flags |= STOPSPRESSUREDAMAGE
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else if(GLOB.ratvar_approaches)
- armor = getArmor(melee = 70, bullet = 80, laser = -15, energy = 25, bomb = 70, bio = 0, rad = 0, fire = 100, acid = 100)
+ armor = getArmor(melee = 70, bullet = 80, laser = 10, energy = 25, bomb = 70, bio = 0, rad = 0, fire = 100, acid = 100, magic = 70, wound = 75)
clothing_flags |= STOPSPRESSUREDAMAGE
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else
- armor = getArmor(melee = 60, bullet = 70, laser = -25, energy = 0, bomb = 60, bio = 0, rad = 0, fire = 100, acid = 100)
+ armor = getArmor(melee = 60, bullet = 70, laser = 0, energy = 0, bomb = 60, bio = 0, rad = 0, fire = 100, acid = 100, magic = 60, wound = 65)
clothing_flags &= ~STOPSPRESSUREDAMAGE
max_heat_protection_temperature = initial(max_heat_protection_temperature)
min_cold_protection_temperature = initial(min_cold_protection_temperature)
@@ -135,7 +135,7 @@
siemens_coefficient = 0
permeability_coefficient = 0.05
resistance_flags = FIRE_PROOF | ACID_PROOF
- armor = list("melee" = 80, "bullet" = 70, "laser" = -25, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 80, "bullet" = 70, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100, "magic" = 70, "wound" = 85)
/obj/item/clothing/gloves/clockwork/Initialize()
. = ..()
@@ -153,7 +153,7 @@
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
else
- armor = getArmor(melee = 80, bullet = 70, laser = -25, energy = 0, bomb = 60, bio = 0, rad = 0, fire = 100, acid = 100)
+ armor = getArmor(melee = 80, bullet = 70, laser = 0, energy = 0, bomb = 60, bio = 0, rad = 0, fire = 100, acid = 100, magic = 70, wound = 85)
clothing_flags &= ~STOPSPRESSUREDAMAGE
max_heat_protection_temperature = initial(max_heat_protection_temperature)
min_cold_protection_temperature = initial(min_cold_protection_temperature)
diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm
index 5837ac302d..0bae7d3539 100644
--- a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm
@@ -1,10 +1,10 @@
/obj/item/clockwork/slab //Clockwork slab: The most important tool in Ratvar's arsenal. Allows scripture recital, tutorials, and generates components.
name = "clockwork slab"
desc = "A strange metal tablet. A clock in the center turns around and around."
- clockwork_desc = "A link between you and the Celestial Derelict. It contains information, recites scripture, and is your most vital tool as a Servant. \
+ clockwork_desc = "A link between you and the Celestial Derelict. It contains information, recites scripture, and is your most vital tool as a Servant.\
It can be used to link traps and triggers by attacking them with the slab. Keep in mind that traps linked with one another will activate in tandem!"
- icon_state = "dread_ipad"
+ icon_state = "clockwork_slab"
lefthand_file = 'icons/mob/inhands/antag/clockwork_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/clockwork_righthand.dmi'
var/inhand_overlay //If applicable, this overlay will be applied to the slab's inhand
@@ -15,13 +15,13 @@
var/busy //If the slab is currently being used by something
var/no_cost = FALSE //If the slab is admin-only and needs no components and has no scripture locks
var/speed_multiplier = 1 //multiples how fast this slab recites scripture
- var/selected_scripture = SCRIPTURE_DRIVER
+ // var/selected_scripture = SCRIPTURE_DRIVER //handled UI side
var/obj/effect/proc_holder/slab/slab_ability //the slab's current bound ability, for certain scripture
- var/recollecting = FALSE //if we're looking at fancy recollection
+ var/recollecting = TRUE //if we're looking at fancy recollection. tutorial enabled by default
var/recollection_category = "Default"
- var/list/quickbound = list(/datum/clockwork_scripture/abscond, \
+ var/list/quickbound = list(/datum/clockwork_scripture/spatial_gateway, \
/datum/clockwork_scripture/ranged_ability/kindle, /datum/clockwork_scripture/ranged_ability/hateful_manacles) //quickbound scripture, accessed by index
var/maximum_quickbound = 5 //how many quickbound scriptures we can have
@@ -36,6 +36,11 @@
speed_multiplier = 0
no_cost = TRUE
+/obj/item/clockwork/slab/debug/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
+ if(!is_servant_of_ratvar(user))
+ add_servant_of_ratvar(user)
+ return ..()
+
/obj/item/clockwork/slab/traitor
var/spent = FALSE
@@ -54,12 +59,6 @@
to_chat(user, "[src] falls dark. It appears you weren't worthy.")
return ..()
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/clockwork/slab/debug/attack_hand(mob/living/user)
- if(!is_servant_of_ratvar(user))
- add_servant_of_ratvar(user)
- return ..()
-
/obj/item/clockwork/slab/cyborg //three scriptures, plus a spear and fabricator
clockwork_desc = "A divine link to the Celestial Derelict, allowing for limited recital of scripture."
quickbound = list(/datum/clockwork_scripture/ranged_ability/judicial_marker, /datum/clockwork_scripture/ranged_ability/linked_vanguard, \
@@ -67,29 +66,32 @@
maximum_quickbound = 6 //we usually have one or two unique scriptures, so if ratvar is up let us bind one more
actions_types = list()
-/obj/item/clockwork/slab/cyborg/engineer //three scriptures, plus a fabricator
- quickbound = list(/datum/clockwork_scripture/abscond, /datum/clockwork_scripture/create_object/replicant, /datum/clockwork_scripture/create_object/sigil_of_transmission, /datum/clockwork_scripture/create_object/stargazer)
+/obj/item/clockwork/slab/cyborg/engineer //six scriptures, plus a fabricator. Might revert this if its too OP, I just thought that engineering borgs should get the all the structures
+ quickbound = list(/datum/clockwork_scripture/spatial_gateway, /datum/clockwork_scripture/create_object/replicant, /datum/clockwork_scripture/create_object/sigil_of_transmission, /datum/clockwork_scripture/create_object/stargazer, \
+ /datum/clockwork_scripture/create_object/ocular_warden, /datum/clockwork_scripture/create_object/clockwork_obelisk, /datum/clockwork_scripture/create_object/mania_motor)
-/obj/item/clockwork/slab/cyborg/medical //five scriptures, plus a spear
- quickbound = list(/datum/clockwork_scripture/abscond, /datum/clockwork_scripture/ranged_ability/linked_vanguard, /datum/clockwork_scripture/ranged_ability/sentinels_compromise, \
- /datum/clockwork_scripture/create_object/vitality_matrix)
+/obj/item/clockwork/slab/cyborg/medical //six scriptures, plus a spear
+ quickbound = list(/datum/clockwork_scripture/spatial_gateway, /datum/clockwork_scripture/ranged_ability/linked_vanguard, /datum/clockwork_scripture/ranged_ability/sentinels_compromise, \
+ /datum/clockwork_scripture/create_object/vitality_matrix, /datum/clockwork_scripture/channeled/mending_mantra)
-/obj/item/clockwork/slab/cyborg/security //twoscriptures, plus a spear
- quickbound = list(/datum/clockwork_scripture/abscond, /datum/clockwork_scripture/ranged_ability/hateful_manacles, /datum/clockwork_scripture/ranged_ability/judicial_marker)
-
-/obj/item/clockwork/slab/cyborg/peacekeeper //two scriptures, plus a spear
- quickbound = list(/datum/clockwork_scripture/abscond, /datum/clockwork_scripture/ranged_ability/hateful_manacles, /datum/clockwork_scripture/ranged_ability/judicial_marker)
+/obj/item/clockwork/slab/cyborg/security //four scriptures, plus a spear
+ quickbound = list(/datum/clockwork_scripture/spatial_gateway, /datum/clockwork_scripture/channeled/volt_blaster, /datum/clockwork_scripture/ranged_ability/hateful_manacles, \
+ /datum/clockwork_scripture/ranged_ability/judicial_marker, /datum/clockwork_scripture/channeled/belligerent)
+/obj/item/clockwork/slab/cyborg/peacekeeper //four scriptures, plus a spear
+ quickbound = list(/datum/clockwork_scripture/spatial_gateway, /datum/clockwork_scripture/channeled/volt_blaster, /datum/clockwork_scripture/ranged_ability/hateful_manacles, \
+ /datum/clockwork_scripture/ranged_ability/judicial_marker, /datum/clockwork_scripture/channeled/belligerent)
+/*//this module was commented out so why wasn't this?
/obj/item/clockwork/slab/cyborg/janitor //six scriptures, plus a fabricator
- quickbound = list(/datum/clockwork_scripture/abscond, /datum/clockwork_scripture/create_object/replicant, /datum/clockwork_scripture/create_object/sigil_of_transgression, \
+ quickbound = list(/datum/clockwork_scripture/spatial_gateway, /datum/clockwork_scripture/create_object/replicant, /datum/clockwork_scripture/create_object/sigil_of_transgression, \
/datum/clockwork_scripture/create_object/stargazer, /datum/clockwork_scripture/create_object/ocular_warden, /datum/clockwork_scripture/create_object/mania_motor)
-
+*/
/obj/item/clockwork/slab/cyborg/service //six scriptures, plus xray vision
- quickbound = list(/datum/clockwork_scripture/abscond, /datum/clockwork_scripture/create_object/replicant,/datum/clockwork_scripture/create_object/stargazer, \
+ quickbound = list(/datum/clockwork_scripture/spatial_gateway, /datum/clockwork_scripture/create_object/replicant,/datum/clockwork_scripture/create_object/stargazer, \
/datum/clockwork_scripture/spatial_gateway, /datum/clockwork_scripture/create_object/clockwork_obelisk)
-/obj/item/clockwork/slab/cyborg/miner //two scriptures, plus a spear and xray vision
- quickbound = list(/datum/clockwork_scripture/abscond, /datum/clockwork_scripture/ranged_ability/linked_vanguard, /datum/clockwork_scripture/spatial_gateway)
+/obj/item/clockwork/slab/cyborg/miner //three scriptures, plus a spear and xray vision
+ quickbound = list(/datum/clockwork_scripture/spatial_gateway, /datum/clockwork_scripture/ranged_ability/linked_vanguard, /datum/clockwork_scripture/channeled/belligerent, /datum/clockwork_scripture/channeled/volt_blaster)
/obj/item/clockwork/slab/cyborg/access_display(mob/living/user)
if(!GLOB.ratvar_awakens)
@@ -140,14 +142,15 @@
/obj/item/clockwork/slab/examine(mob/user)
. = ..()
- if(is_servant_of_ratvar(user) || isobserver(user))
- if(LAZYLEN(quickbound))
- for(var/i in 1 to quickbound.len)
- if(!quickbound[i])
- continue
- var/datum/clockwork_scripture/quickbind_slot = quickbound[i]
- . += "Quickbind button: [initial(quickbind_slot.name)]."
- . += "Available power:[DisplayPower(get_clockwork_power())]."
+ if(!is_servant_of_ratvar(user) || !isobserver(user))
+ return
+ if(LAZYLEN(quickbound))
+ for(var/i in 1 to quickbound.len)
+ if(!quickbound[i])
+ continue
+ var/datum/clockwork_scripture/quickbind_slot = quickbound[i]
+ . += "Quickbind button: [initial(quickbind_slot.name)]."
+ . += "Available power: [DisplayPower(get_clockwork_power())]."
//Slab actions; Hierophant, Quickbind
/obj/item/clockwork/slab/ui_action_click(mob/user, action)
@@ -165,18 +168,19 @@
user.emote("scream")
user.apply_damage(5, BURN, BODY_ZONE_L_ARM)
user.apply_damage(5, BURN, BODY_ZONE_R_ARM)
- return 0
+ return FALSE
if(!is_servant_of_ratvar(user))
to_chat(user, "The information on [src]'s display shifts rapidly. After a moment, your head begins to pound, and you tear your eyes away.")
- user.confused += 5
- user.dizziness += 5
- return 0
+ if(user.confused || user.dizziness)
+ user.confused += 5
+ user.dizziness += 5
+ return FALSE
if(busy)
to_chat(user, "[src] refuses to work, displaying the message: \"[busy]!\"")
- return 0
+ return FALSE
if(!no_cost && !can_recite_scripture(user))
to_chat(user, "[src] hums fitfully in your hands, but doesn't seem to do anything...")
- return 0
+ return FALSE
access_display(user)
/obj/item/clockwork/slab/AltClick(mob/living/user)
@@ -192,14 +196,6 @@
ui_interact(user)
return TRUE
-/obj/item/clockwork/slab/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "clockwork_slab", name, 800, 420, master_ui, state)
- ui.set_autoupdate(FALSE) //we'll update this occasionally, but not as often as possible
- ui.set_style("clockwork")
- ui.open()
-
/obj/item/clockwork/slab/proc/recite_scripture(datum/clockwork_scripture/scripture, mob/living/user)
if(!scripture || !user || !user.canUseTopic(src) || (!no_cost && !can_recite_scripture(user)))
return FALSE
@@ -207,294 +203,156 @@
to_chat(user, "You need to hold the slab in your active hand to recite scripture!")
return FALSE
var/initial_tier = initial(scripture.tier)
- if(initial_tier != SCRIPTURE_PERIPHERAL)
- if(!GLOB.ratvar_awakens && !no_cost && !SSticker.scripture_states[initial_tier])
- to_chat(user, "That scripture is not unlocked, and cannot be recited!")
- return FALSE
+ if(initial_tier == SCRIPTURE_PERIPHERAL)
+ to_chat(user, "Nice try using href exploits")
+ return
+ if(!GLOB.ratvar_awakens && !no_cost && !SSticker.scripture_states[initial_tier])
+ to_chat(user, "That scripture is not unlocked, and cannot be recited!")
+ return FALSE
var/datum/clockwork_scripture/scripture_to_recite = new scripture
scripture_to_recite.slab = src
scripture_to_recite.invoker = user
scripture_to_recite.run_scripture()
return TRUE
-
-//Guide to Serving Ratvar
-/obj/item/clockwork/slab/proc/recollection()
- var/list/textlist = list("If you're seeing this, file a bug report.")
- if(GLOB.ratvar_awakens)
- textlist = list("")
- for(var/i in 1 to 100)
- textlist += "HONOR RATVAR "
- textlist += ""
- else
- textlist = list("
[text2ratvar("Purge all untruths and honor Engine.")]
\
- \
- NOTICE: This information is out of date. Read the Ark & You primer in your backpack or read the wiki page for current info. \
- \
- These pages serve as the archives of Ratvar, the Clockwork Justiciar. This section of your slab has information on being as a Servant, advice for what to do next, and \
- pointers for serving the master well. You should recommended that you check this area for help if you get stuck or need guidance on what to do next.
\
- \
- Disclaimer: Many objects, terms, and phrases, such as Servant, Cache, and Slab, are capitalized like proper nouns. This is a quirk of the Ratvarian language; \
- do not let it confuse you! You are free to use the names in pronoun form when speaking in normal languages. ")
- return textlist.Join()
-
-//Gets text for a certain section. "Default" is used for when you first open Recollection.
-//Current sections (make sure to update this if you add one:
-//- Basics
-//- Terminology
-//- Components
-//- Scripture
-//- Power
-//- Conversion
-/obj/item/clockwork/slab/proc/get_recollection_text(section)
- var/list/dat = list()
- switch(section)
+/*
+ * Gets text for a certain section. "Default" is used for when you first open Recollection.
+ * Current sections (make sure to update this if you add one:
+ * Basics
+ * Terminology
+ * Components
+ * Scripture
+ * Power
+ * Conversion
+ * * what - What section?
+ */
+/obj/item/clockwork/slab/proc/get_recollection(what) //Now DMDOC compliant!*
+ . = list()
+ switch(what) //need someone to rewrite info for this.
if("Default")
- dat += "You can browse the above sections as you please. They're designed to be read in order, but feel free to pick and choose between them."
- if("Getting Started")
- dat += "Getting Started
"
- dat += "Welcome, Servant! This section houses the utmost basics of being a Servant of Ratvar, and is much more informal than the other sections. Being a Servant of \
- Ratvar is a very complex role, with many systems, objects, and resources to use effectively and creatively.
"
- dat += "This section of your clockwork slab covers everything that Servants have to be aware of, but is a long read because of how in-depth the systems are. Knowing \
- how to use the tools at your disposal makes all the difference between a clueless Servant and a great one.
"
- dat += "If this is your first time being a Servant, relax. It's very much possible that you'll fail, but it's impossible to learn without making mistakes. For the time \
- being, use the Hierophant Network button in the top left-hand corner of your screen to try and get in touch with your fellow Servants; ignore the others for now. This button \
- will let you send messages across space and time to all other Servants. This makes it great for coordinating, and you should use it often! Note: Using \
- this will cause you to whisper your message aloud, so doing so in a public place is very suspicious and you should try to restrict it to private use.
"
- dat += "If you aren't willing or don't have the time to read through every section, you can still help your teammates! Ask if they've set up a base. If they have, head there \
- and ask however you can help; chances are there's always something. If not, it's your job as a Servant to get one up and running! Try to find a secluded, low-traffic area, \
- like the auxiliary base or somewhere deep in maintenance. You'll want to go into the Drivers section of the slab and look for Tinkerer's Cache. Find a nice spot and \
- create one. This serves as a storage for components, the cult's primary resource. (Your slab's probably produced a few by now.) By attacking that cache with this \
- slab, you'll offload all your components into it, and all Servants will be able to use those components from any distance - all Tinkerer's Caches are linked!
"
- dat += "Once you have a base up and running, contact your fellows and let them know. You should come back here often to drop off the slab's components, and your fellows \
- should do the same, either in this cache or in ones of their own.
"
- dat += "If you think you're confident in taking further steps to help the cult, feel free to move onto the other sections. If not, let your allies know that you're new and \
- would appreciate the help they might offer you. Most experienced Servants would be happy to help; if everyone is inexperienced, then you'll have to step out of your comfort \
- zone and read onto the other sections. It's very likely that you might fail, but don't worry too much about it; you can't learn effectively without making mistakes.
"
- dat += "For now, welcome! If you're looking to learn, you should start with the Basics section, then move onto Components and Scripture. At the very \
- least, you should read the Conversion section, as it outlines the most important aspects of being a Servant. Good luck!
"
- dat += "-=-=-=-=-=-"
+ .["title"] = "Default"
+ .["info"] = "Hello servant! Currently these categories dosen't work!"
+ /*
if("Basics")
- dat += "Servant Basics
"
- dat += "The first thing any Servant should know is their slab, inside and out. The clockwork slab is by far your most important tool. It allows you to speak with your \
- fellow Servants, create components that fuel many of your abilities, use those abilities, and should be kept safe and hidden on your person at all times. If you have not \
- done so already, it's a good idea to check for any fellow Servants using the Hierophant Network button in the top-left corner of your screen; due to the cult's nature, \
- teamwork is an instrumental component of your success.
" //get it? component? ha!
- dat += "As a Servant of Ratvar, the tools you are given focus around building and maintaining bases and outposts. A great deal of your power comes from stationary \
- structures, and without constructing a base somewhere, it's essentially impossible to succeed. Finding a good spot to build a base can be difficult, and it's recommended \
- that you choose an area in low-traffic part of the station (such as the auxiliary base). Make sure to disconnect any cameras in the area beforehand.
"
- dat += "Because of how complex being a Servant is, it isn't possible to fit much information into this section. It's highly recommended that you read the Components \
- and Scripture sections next. Not knowing how these two systems work will cripple both you and your fellows, and lead to a frustrating experience for everyone.
"
- dat += "-=-=-=-=-=-"
+ .["title"] = "Basics"
+ .["info"] = "# MARKDOWN WITH HTML?"
if("Terminology")
- dat += "Common Servant Terminology "
- dat += "This isn't intended to be read all at once; you are advised to treat it moreso as a glossary.
"
- dat += "General "
- dat += "Servant: A person or robot who serves Ratvar. You are one of these. "
- dat += "Cache: A Tinkerer's Cache, which is a structure that stores and creates components. "
- dat += "CV: Construction Value. All clockwork structures, floors, and walls increase this number. "
- dat += "Vitality: Used for healing effects, produced by Ratvarian spear attacks and Vitality Matrices. "
- dat += "Geis: An important scripture used to make normal crew and robots into Servants of Ratvar. "
- dat += "Ark: The cult's win condition, a huge structure that needs to be defended.
"
- dat += "Items "
- dat += "Slab: A clockwork slab, a Servant's most important tool. You're holding one! Keep it safe and hidden. "
- dat += "Visor: A judicial visor, which is a pair of glasses that can smite an area for a brief stun and delayed explosion. "
- dat += "Wraith Specs: Wraith spectacles, which provide true sight (X-ray, night vision) but damage the wearer's eyes. "
- dat += "Spear: A Ratvarian spear, which is a very powerful melee weapon that produces Vitality. "
- dat += "Fabricator: A replica fabricator, which converts objects into clockwork versions.
"
- dat += "Constructs "
- dat += "Marauder: A clockwork marauder, which is a powerful bodyguard that hides in its owner.
"
- dat += "Structures (* = requires power) "
- dat += "Warden: An ocular warden, which is a ranged turret that damages non-Servants that see it. "
- dat += "Prism*: A prolonging prism, which delays the shuttle for two minutes at a huge power cost.
"
- dat += "Motor*: A mania motor, which serves as area-denial through negative effects and eventual conversion. "
- dat += "Daemon*: A tinkerer's daemon, which quickly creates components. "
- dat += "Obelisk*: A clockwork obelisk, which can broadcast large messages and allows limited teleportation. "
- dat += "Sigils "
- dat += "Note: Sigils can be stacked on top of one another, making certain sigils very effective when paired! "
- dat += "Transgression: Stuns the first non-Servant to cross it for ten seconds and blinds others nearby. Disappears on use. "
- dat += "Submission: Converts the first non-Servant to stand on the sigil for seven seconds. Disappears on use. "
- dat += "Matrix: Drains health from non-Servants, producing Vitality. Can heal and revive Servants. "
- dat += "Accession: Identical to the Sigil of Submission, but doesn't disappear on use. It can also convert a single mindshielded target, but will disappear after doing this. "
- dat += "Transmission: Drains and stores power for clockwork structures. Feeding it brass sheets will create additional power.
"
- dat += "-=-=-=-=-=-"
+ .["title"] = "Terminology"
+ .["info"] = "# MARKDOWN WITH HTML?"
if("Components")
- dat += "Components & Their Uses
"
- dat += "Components are your primary resource as a Servant. There are five types of component, with each one being used in different roles:
"
- dat += "Although this is a good rule of thumb, their effects become much more nuanced when used together. For instance, a turret might have both belligerent eyes and \
- vanguard cogwheels as construction requirements, because it defends its allies by harming its enemies.
"
- dat += "Components' primary use is fueling scripture (covered in its own section), and they can be created through various ways. This clockwork slab, for instance, \
- will make a random component of every type - or a specific one, if you choose a target component from the interface - every remove me already. This number will increase \
- as the amount of Servants in the covenant increase; additionally, slabs can only produce components when held by a Servant, and holding more than one slab will cause both \
- of them to halt progress until one of them is removed from their person.
"
- dat += "Your slab has an internal storage of components, but it isn't meant to be the main one. Instead, there's a global storage of components that can be \
- added to through various ways. Anything that needs components will first draw them from the global storage before attempting to draw them from the slab. Most methods of \
- component production add to the global storage. You can also offload components from your slab into the global storage by using it on a Tinkerer's Cache, a structure whose \
- primary purpose is to do just that (although it will also slowly produce components when placed near a brass wall.)
"
- dat += "-=-=-=-=-=-"
+ .["title"] = "Default"
+ .["info"] = "# MARKDOWN WITH HTML?"
if("Scripture")
- dat += "The Ancient Scripture
"
- dat += "If you have experience with the Nar'Sian cult (or the \"blood cult\") then you will know of runes. They are the manifestations of the Geometer's power, and where most \
- of the cult's supernatural ability comes from. The Servant equivalent of runes is called scripture, and unlike runes, scripture is loaded into your clockwork slab.
"
- dat += "Each piece of scripture has widely-varying effects. Your most important scripture, Geis, is obvious and suspicious, but charges your slab with energy and allows \
- you to attack a non-Servant in melee range to restrain them and begin converting them into a Servant. This is just one example; each piece of scripture can be simple or \
- complex, be obvious or have hidden mechanics that can only be found through trial and error.
"
- dat += "Any given piece of scripture has a component cost listed in its \"Recite\" button. The acronyms for the components should be obvious if you've read about components \
- already; reciting this piece of scripture will consume the listed components, first from the global storage and then from your slab. Note that failing to recite a piece of \
- scripture will not consume the components required to recite it.
"
- dat += "It should also be noted that some scripture cannot be recited alone. Especially with more powerful scripture, you may need multiple Servants to recite a piece of \
- scripture; both of you will need to stand still until the recital completes. Only human and silicon Servants are valid for scripture recital! Constructs cannot help \
- in reciting scripture.
"
- dat += "Finally, scripture is separated into three \"tiers\" based on power: Drivers, Scripts, and Applications.[prob(1) ? " (The Revenant tier was removed a long time ago. \
- Get with the times.)" : ""] You can view the requirements to unlock each tier in its scripture list. Once a tier is unlocked, it's unlocked permanently; the cult only needs to fill the \
- requirement for unlocking a tier once!
"
- dat += "-=-=-=-=-=-"
+ .["title"] = "Default"
+ .["info"] = "# MARKDOWN WITH HTML?"
if("Power")
- dat += "Power! Unlimited Power!
"
- dat += "In the early stages of the cult, the only resource that must be actively worried about is components. However, as new scripture is unlocked, a new resource \
- becomes necessary: power. Almost all clockwork structures require power to function in some way. There is nothing special about this power; it's mere electricity, \
- and can be harnessed in several ways.
"
- dat += "To begin with, if there is no other source of power nearby, structures will draw from the area's APC, assuming it has one. This is inefficient and ill-advised as \
- anything but a last resort. Instead, it is recommended that a Sigil of Transmission is created. This sigil serves as both battery and power generator for nearby clockwork \
- structures, and those structures will happily draw power from the sigil before they resort to APCs.
"
- dat += "Generating power is less easy. The most reliable and efficient way is using brass sheets; attacking a sigil of transmission with brass sheets will convert them \
- to power, at a rate of [DisplayPower(POWER_FLOOR)] per sheet. (Brass sheets are created from replica fabricators, which are explained more in detail in the Conversion section.) \
- Activating a sigil of transmission will also cause it to drain power from the nearby area, which, while effective, serves as an obvious tell that there is something wrong.
"
- dat += "Without power, many structures will not function, making a base vulnerable to attack. For this reason, it is critical that you keep an eye on your power reserves and \
- ensure that they remain comfortably high.
"
- dat += "-=-=-=-=-=-"
+ .["title"] = "Power"
+ .["info"] = "# MARKDOWN WITH HTML?"
if("Conversion")
- dat += "Growing the Ranks
"
- dat += "Because the Servants of Ratvar are a cult, the main method to gain more power is to \"enlighten\" normal crew into new Servants. When a crewmember is converted, \
- they become a full-fledged Servant, ready and willing to serve the cause of Ratvar. It should also be noted that silicon crew, such as cyborgs and the AI, can be \
- converted just like normal crew and will gain special abilities; this is covered later. This section will also cover converting the station's structure itself; walls, \
- floors, windows, tables, and other objects can all be converted into clockwork versions, and serve an important purpose.
"
- dat += "A Note on Geis: There are several ways to convert humans and silicons. However, the most important tool to making them work is \
- Geis, a Driver-tier scripture. Using it whispers an invocation very quickly and charges your slab with power. In addition to making the slab visible in your hand, \
- you can now use it on a target within melee range to bind and mute them. It is by far your most reliable tool for capturing potential converts and targets, though it is incredibly \
- obvious. In addition, you are unable to take any actions other than moving while your target is bound. The binding will last for 25 seconds and mute for about 13 seconds, though \
- allies can use Geis to refresh these effects.
"
- dat += "Converting: The two methods of conversion are the sigil of submission, whose purpose is to do so, and the mania motor. \
- The sigil of submission is a sigil that, when stood on by a non-Servant for eight seconds, will convert that non-Servant. This is the only practical way to convert targets. \
- Sigils of submission are cheap, early, and permanent! Make sure sigils of submission are placed only in bases or otherwise hidden spots, or with a sigil of transgression on them. \
- The mania motor, however, is generally unreliable and unlocked later, only converting those who stand near it for an extended period.
"
- dat += "Converting Humans: For obvious reasons, humans are the most common conversion target. Because every crew member is different, and \
- may be armed with different equipment, you should take precautions to ensure that they aren't able to resist. If able, removing a headset is essential, as is restraining \
- them through handcuffs, cable ties, or other restraints. Some crew, like security, are also implanted with mindshield implants; these will prevent conversion and must be \
- surgically removed before they are an eligible convert. Note: The captain is never an eligible convert and should instead be killed or imprisoned. If security \
- begins administering mindshield implants, this will greatly inhibit conversion. Also note that mindshield implants can be broken by a sigil of accession automatically, but \
- the sigil will disappear.
"
- dat += "Converting Silicons: Due to their robotic nature, silicons are generally more predictable than humans in terms of conversion. \
- However, they are also much, much harder to subdue, especially cyborgs. The easiest way to convert a cyborg is by using Geis to restrain them, then dragging them to a sigil \
- of submission. If you stack a sigil of transgression and a sigil of submission, a crossing cyborg will be stunned and helpless to escape before they are converted.
"
- dat += "Converting AIs is very often the hardest task of the cult, and has been the downfall of countless successful Servants. Their omnipresence across the station, \
- coupled with their secure location and ability to lock themselves securely, makes them a powerful target. However, once the AI itself is reached, it is usually completely \
- helpless to resist its own conversion. A very common tactic is to take advantage of a converted cyborg to rush the AI before it is able to react.
"
- dat += "Even once an AI is converted, care must be taken to ensure that it remains hidden. Not only does the AI's core become brassy and thus obvious to an outside \
- observer, but the AI loses the ability to speak in anything but Ratvarian. For this reason, it has to remain completely silent over common radio channels if stealth \
- is at all a priority. This is suspicious and will rapidly lead to the crew checking on it, which usually results in the cult's outing. It is, however, necessary to convert \
- all AIs present on the station before the Ark becomes invokable, so this must be done at some point.
"
- dat += "Converting the Station: Converted objects all serve a purpose and are important to the cult's success. To convert objects, \
- a Servant needs to use a replica fabricator, a handheld tool that uses power to replace objects with clockwork versions. Different clockwork objects have different \
- effects and are often crucial. The most noteworthy are clockwork walls, which automatically \"link\" to any nearby Tinkerer's Caches, causing them to slowly \
- generate components. This is incredibly useful for obvious reasons, and creating a clockwork wall near every Tinkerer's Cache should be prioritized. Clockwork floors \
- will slowly heal any toxin damage suffered by Servants standing on them, and clockwork airlocks can only be opened by Servants.
"
- dat += "The replica fabricator itself is also worth noting. In addition to replacing objects, it can also create brass sheets at the cost of power by using the \
- fabricator in-hand. It can also be used to repair any damaged clockwork structures.
"
- dat += "Replacing objects is almost as, if not as important as, converting new Servants. A base is impossible to manage without clockwork walls at the very least, and \
- once the cult has been outed and the crew are actively searching, there is little reason not to use as many as possible.
"
- dat += "-=-=-=-=-=-"
+ .["title"] = "Conversion"
+ .["info"] = "# MARKDOWN WITH HTML?"
+ */
else
- dat += "404: [section ? section : "Section"] Not Found!
\
- One of the cogscarabs must've misplaced this section, because the game wasn't able to find any info regarding it. Report this to the coders!"
- return "
[dat.Join()]
"
-
-//Gets the quickbound scripture as a text block.
-/obj/item/clockwork/slab/proc/get_recollection_quickbinds()
- var/list/dat = list()
- dat += "Quickbound Scripture \
- You can have up to five scriptures bound to action buttons for easy use.
"
- if(LAZYLEN(quickbound))
- for(var/i in 1 to maximum_quickbound)
- if(LAZYLEN(quickbound) < i || !quickbound[i])
- dat += "A Quickbind slot, currently set to Nothing. "
- else
- var/datum/clockwork_scripture/quickbind_slot = quickbound[i]
- dat += "A Quickbind slot, currently set to [initial(quickbind_slot.name)]. "
- return dat.Join()
+ return null //error text handled tgui side. should not cause BSOD
+/obj/item/clockwork/slab/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ClockworkSlab", name)
+ ui.open()
/obj/item/clockwork/slab/ui_data(mob/user) //we display a lot of data via TGUI
- var/list/data = list()
- data["power"] = "[DisplayPower(get_clockwork_power())] power is available for scripture and other consumers."
-
- switch(selected_scripture) //display info based on selected scripture tier
- if(SCRIPTURE_DRIVER)
- data["tier_info"] = "These scriptures are permanently unlocked."
- if(SCRIPTURE_SCRIPT)
- if(SSticker.scripture_states[SCRIPTURE_SCRIPT])
- data["tier_info"] = "These scriptures are permanently unlocked."
- else
- data["tier_info"] = "These scriptures will automatically unlock when the Ark is halfway ready or if [DisplayPower(SCRIPT_UNLOCK_THRESHOLD)] of power is reached."
- if(SCRIPTURE_APPLICATION)
- if(SSticker.scripture_states[SCRIPTURE_APPLICATION])
- data["tier_info"] = "These scriptures are permanently unlocked."
- else
- data["tier_info"] = "Unlock these optional scriptures by converting another servant or if [DisplayPower(APPLICATION_UNLOCK_THRESHOLD)] of power is reached.."
-
- data["selected"] = selected_scripture
- data["scripturecolors"] = "Scriptures in yellow are related to construction and building. \
- Scriptures in red are related to attacking and offense. \
- Scriptures in blue are related to healing and defense. \
- Scriptures in purple are niche but still important! \
- Scriptures with italicized names are important to success."
- generate_all_scripture()
-
- data["scripture"] = list()
- for(var/s in GLOB.all_scripture)
+ . = list()
+ .["recollection"] = recollecting
+ .["power"] = DisplayPower(get_clockwork_power())
+ .["power_unformatted"] = get_clockwork_power()
+ .["HONOR_RATVAR"] = GLOB.ratvar_awakens
+ .["scripture"] = list()
+ for(var/s in GLOB.all_scripture) //don't block this, even when ratvar spawns for roundend griff.
var/datum/clockwork_scripture/S = GLOB.all_scripture[s]
- if(S.tier == selected_scripture) //display only scriptures of the selected tier
- var/scripture_color = get_component_color_bright(S.primary_component)
- var/list/temp_info = list("name" = "[S.name]",
- "descname" = "([S.descname])",
- "tip" = "[S.desc]\n[S.usage_tip]",
- "required" = "([DisplayPower(S.power_cost)][S.special_power_text ? "+ [replacetext(S.special_power_text, "POWERCOST", "[DisplayPower(S.special_power_cost)]")]" : ""])",
- "type" = "[S.type]",
- "quickbind" = S.quickbind)
- if(S.important)
- temp_info["name"] = "[temp_info["name"]]"
- var/found = quickbound.Find(S.type)
- if(found)
- temp_info["bound"] = "[found]"
- if(S.invokers_required > 1)
- temp_info["invokers"] = "Invokers: [S.invokers_required]"
- data["scripture"] += list(temp_info)
- data["recollection"] = recollecting
- if(recollecting)
- data["recollection_categories"] = GLOB.ratvar_awakens ? list() : list(\
- list("name" = "Getting Started", "desc" = "First-time servant? Read this first."), \
- list("name" = "Basics", "desc" = "A primer on how to play as a servant."), \
- list("name" = "Terminology", "desc" = "Common acronyms, words, and terms."), \
- list("name" = "Components", "desc" = "Information on components, your primary resource."), \
- list("name" = "Scripture", "desc" = "Information on scripture, ancient tools used by the cult."), \
- list("name" = "Power", "desc" = "The power system that certain objects use to function."), \
- list("name" = "Conversion", "desc" = "Converting the crew, cyborgs, and very walls to your cause."), \
- )
- data["rec_text"] = recollection()
- data["rec_section"] = GLOB.ratvar_awakens ? "" : get_recollection_text(recollection_category)
- data["rec_binds"] = GLOB.ratvar_awakens ? "" : get_recollection_quickbinds()
- return data
+ if(S.tier == SCRIPTURE_PERIPHERAL) // This tier is skiped because this contains basetype stuff
+ continue
+
+ var/list/data = list()
+ data["name"] = S.name
+ data["descname"] = S.descname
+ data["tip"] = "[S.desc]\n[S.usage_tip]"
+ data["required"] = "([DisplayPower(S.power_cost)][S.special_power_text ? "+ [replacetext(S.special_power_text, "POWERCOST", "[DisplayPower(S.special_power_cost)]")]" : ""])"
+ data["required_unformatted"] = S.power_cost
+ data["type"] = "[S.type]"
+ data["quickbind"] = S.quickbind //this is if it cant quickbind (bool)
+ data["fontcolor"] = get_component_color_bright(S.primary_component)
+ data["important"] = S.important //italic!
+
+ var/found = quickbound.Find(S.type)
+ if(found)
+ data["bound"] = found //number (pos) on where is it on the list
+ if(S.invokers_required > 1)
+ data["invokers"] = "Invokers: [S.invokers_required]"
+
+ .["rec_binds"] = list()
+ for(var/i in 1 to maximum_quickbound)
+ if(LAZYLEN(quickbound) < i || !quickbound[i])
+ .["rec_binds"] += list(list()) //a blank json.
+ else
+ var/datum/clockwork_scripture/quickbind_slot = quickbound[i]
+ .["rec_binds"] += list(list(
+ "name" = initial(quickbind_slot.name),
+ "color" = get_component_color_bright(initial(quickbind_slot.primary_component))
+ ))
+
+ .["scripture"][S.tier] += list(data)
+
+/obj/item/clockwork/slab/ui_static_data(mob/user)
+ . = list()
+ .["tier_infos"] = list() //HEY!! WHEN ADDING NEW TIER, ADD IT HERE
+ .["tier_infos"][SCRIPTURE_PERIPHERAL] = list(
+ "requirement" = "Breaking the code DM side. Report to coggerbus if this appears!!",
+ "ready" = FALSE //just in case. Should NOT exist at all
+ )
+ .["tier_infos"][SCRIPTURE_DRIVER] = list(
+ "requirement" = "None, this is already unlocked",
+ "ready" = TRUE //to bold it on JS side, and to say "These scriptures are permanently unlocked."
+ )
+ .["tier_infos"][SCRIPTURE_SCRIPT] = list(
+ "requirement" = "These scriptures will automatically unlock when the Ark is halfway ready or if [DisplayPower(SCRIPT_UNLOCK_THRESHOLD)] of power is reached.",
+ "ready" = SSticker.scripture_states[SCRIPTURE_SCRIPT] //huh, on the gamemode ticker? okay...
+ )
+ .["tier_infos"][SCRIPTURE_APPLICATION] = list(
+ "requirement" = "Unlock these optional scriptures by converting another servant or if [DisplayPower(APPLICATION_UNLOCK_THRESHOLD)] of power is reached..",
+ "ready" = SSticker.scripture_states[SCRIPTURE_APPLICATION]
+ )
+ .["tier_infos"][SCRIPTURE_JUDGEMENT] = list(
+ "requirement" = "Unlock powerful equipment and structures by converting five servants or if [DisplayPower(JUDGEMENT_UNLOCK_THRESHOLD)] of power is reached..",
+ "ready" = SSticker.scripture_states[SCRIPTURE_JUDGEMENT]
+ )
+ .["recollection_categories"] = list()
+ if(GLOB.ratvar_awakens)
+ return
+ .["recollection_categories"] = list(
+ list("name" = "Getting Started", "desc" = "First-time servant? Read this first."),
+ list("name" = "Basics", "desc" = "A primer on how to play as a servant."),
+ list("name" = "Terminology", "desc" = "Common acronyms, words, and terms."),
+ list("name" = "Components", "desc" = "Information on components, your primary resource."),
+ list("name" = "Scripture", "desc" = "Information on scripture, ancient tools used by the cult."),
+ list("name" = "Power", "desc" = "The power system that certain objects use to function."),
+ list("name" = "Conversion", "desc" = "Converting the crew, cyborgs, and very walls to your cause.")
+ )
+ .["rec_section"] = get_recollection(recollection_category)
+ generate_all_scripture()
+ //needs a new place to live, preferably when clockcult unlocks/downgrades a tier. Smart enough to earlyreturn.
/obj/item/clockwork/slab/ui_act(action, params)
switch(action)
if("toggle")
recollecting = !recollecting
if("recite")
- INVOKE_ASYNC(src, .proc/recite_scripture, text2path(params["category"]), usr, FALSE)
- if("select")
- selected_scripture = params["category"]
+ INVOKE_ASYNC(src, .proc/recite_scripture, text2path(params["script"]), usr, FALSE)
if("bind")
- var/datum/clockwork_scripture/path = text2path(params["category"]) //we need a path and not a string
+ var/datum/clockwork_scripture/path = text2path(params["script"]) //we need a path and not a string
+ if(!ispath(path, /datum/clockwork_scripture) || !initial(path.quickbind) || initial(path.tier) == SCRIPTURE_PERIPHERAL) //fuck you href bus
+ to_chat(usr, "Nice try using href exploits")
+ return
var/found_index = quickbound.Find(path)
if(found_index) //hey, we already HAVE this bound
if(LAZYLEN(quickbound) == found_index) //if it's the last scripture, remove it instead of leaving a null
@@ -512,8 +370,8 @@
quickbind_to_slot(path, target_index)
if("rec_category")
recollection_category = params["category"]
- ui_interact(usr)
- return 1
+ update_static_data()
+ return TRUE
/obj/item/clockwork/slab/proc/quickbind_to_slot(datum/clockwork_scripture/scripture, index) //takes a typepath(typecast for initial()) and binds it to a slot
if(!ispath(scripture) || !scripture || (scripture in quickbound))
diff --git a/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm b/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm
index 43c05b8556..19c29762bf 100644
--- a/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm
+++ b/code/modules/antagonists/clockcult/clock_items/construct_chassis.dm
@@ -31,8 +31,7 @@
. = ..()
clockwork_desc = initial(clockwork_desc)
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/clockwork/construct_chassis/attack_hand(mob/living/user)
+/obj/item/clockwork/construct_chassis/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
if(w_class >= WEIGHT_CLASS_HUGE)
to_chat(user, "[src] is too cumbersome to carry! Drag it around instead!")
return
diff --git a/code/modules/antagonists/clockcult/clock_items/integration_cog.dm b/code/modules/antagonists/clockcult/clock_items/integration_cog.dm
index 0ce70336fe..ab8e30c8bb 100644
--- a/code/modules/antagonists/clockcult/clock_items/integration_cog.dm
+++ b/code/modules/antagonists/clockcult/clock_items/integration_cog.dm
@@ -30,6 +30,7 @@
var/obj/item/stock_parts/cell/cell = apc.cell
if(cell && (cell.charge / cell.maxcharge > COG_MAX_SIPHON_THRESHOLD))
cell.use(1)
+ apc.cog_drained++
adjust_clockwork_power(2) //Power is shared, so only do it once; this runs very quickly so it's about 10 W/second
else
adjust_clockwork_power(1) //Continue generating power when the cell has run dry; 5 W/second
diff --git a/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm b/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm
index faa5e025ca..2f6a018a4c 100644
--- a/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm
+++ b/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm
@@ -114,24 +114,27 @@
superheat_wall(A)
return
if(modifiers["middle"] || modifiers["ctrl"])
- issue_command(A)
+ INVOKE_ASYNC(src, .proc/issue_command, A)
return
if(GLOB.ark_of_the_clockwork_justiciar == A)
var/obj/structure/destructible/clockwork/massive/celestial_gateway/G = GLOB.ark_of_the_clockwork_justiciar
- if(G.recalling)
- return
- if(!G.recalls_remaining)
- to_chat(src, "The Ark can no longer recall!")
- return
- if(alert(src, "Initiate mass recall?", "Mass Recall", "Yes", "No") != "Yes" || QDELETED(src) || QDELETED(G) || !G.obj_integrity)
- return
- G.initiate_mass_recall() //wHOOPS LOOKS LIKE A HULK GOT THROUGH
+ INVOKE_ASYNC(src, .proc/attempt_recall, G)
else if(istype(A, /obj/structure/destructible/clockwork/trap/trigger))
var/obj/structure/destructible/clockwork/trap/trigger/T = A
T.visible_message("[T] clunks as it's activated remotely.")
to_chat(src, "You activate [T].")
T.activate()
+/mob/camera/eminence/proc/attempt_recall(obj/structure/destructible/clockwork/massive/celestial_gateway/G)
+ if(G.recalling)
+ return
+ if(!G.recalls_remaining)
+ to_chat(src, "The Ark can no longer recall!")
+ return
+ if(alert(src, "Initiate mass recall?", "Mass Recall", "Yes", "No") != "Yes" || QDELETED(src) || QDELETED(G) || !G.obj_integrity)
+ return
+ G.initiate_mass_recall() //wHOOPS LOOKS LIKE A HULK GOT THROUGH
+
/mob/camera/eminence/ratvar_act()
name = "\improper Radiance"
real_name = "\improper Radiance"
diff --git a/code/modules/antagonists/clockcult/clock_mobs/clockwork_marauder.dm b/code/modules/antagonists/clockcult/clock_mobs/clockwork_marauder.dm
index 3bc237fc56..79b919b1e9 100644
--- a/code/modules/antagonists/clockcult/clock_mobs/clockwork_marauder.dm
+++ b/code/modules/antagonists/clockcult/clock_mobs/clockwork_marauder.dm
@@ -124,3 +124,437 @@
#undef MARAUDER_SLOWDOWN_PERCENTAGE
#undef MARAUDER_SHIELD_REGEN_TIME
+
+//Clockwork guardian: Slow but with high damage, resides inside of a servant. Created via the Memory Allocation scripture.
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian
+ name = "clockwork guardian"
+ desc = "A stalwart apparition of a soldier, blazing with crimson flames. It's armed with a gladius and shield and stands ready by its master."
+ icon_state = "clockwork_marauder"
+ health = 300
+ maxHealth = 300
+ speed = 1
+ obj_damage = 40
+ melee_damage_lower = 12
+ melee_damage_upper = 12
+ attack_verb_continuous = "slashes"
+ attack_verb_simple = "slash"
+ attack_sound = 'sound/weapons/bladeslice.ogg'
+ weather_immunities = list("lava")
+ movement_type = FLYING
+ AIStatus = AI_OFF //this has to be manually set so that the guardian doesn't start bashing the host, how annoying -_-
+ loot = list(/obj/item/clockwork/component/geis_capacitor/fallen_armor)
+ max_shield_health = 0
+ shield_health = 0
+ var/true_name = "Meme Master 69" //Required to call forth the guardian
+ var/global/list/possible_true_names = list("Servant", "Warden", "Serf", "Page", "Usher", "Knave", "Vassal", "Escort")
+ var/mob/living/host //The mob that the guardian is living inside of
+ var/recovering = FALSE //If the guardian is recovering from recalling
+ var/blockchance = 17 //chance to block attacks entirely
+ var/counterchance = 30 //chance to counterattack after blocking
+ var/static/list/damage_heal_order = list(OXY, BURN, BRUTE, TOX) //we heal our host's damage in this order
+ light_range = 2
+ light_power = 1.1
+ playstyle_string = "You are a clockwork guardian, a living extension of Sevtug's will. As a guardian, you are somewhat slow, but may block attacks, \
+ and have a chance to also counter blocked melee attacks for extra damage, in addition to being immune to extreme temperatures and pressures. \
+ Your primary goal is to serve the creature that you are now a part of, as well as The Clockwork Justiciar, Ratvar. You can use The Hierophant Network to communicate silently with your master and their allies, \
+ but can only exit if your master calls your true name or if they are exceptionally damaged. \
+ \n\n\
+ Stay near your host to protect and heal them; being too far from your host will rapidly cause you massive damage. Recall to your host if you are too weak and believe you cannot continue \
+ fighting safely. As a final note, you should probably avoid harming any fellow servants of Ratvar."
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/Initialize()
+ . = ..()
+ true_name = pick(possible_true_names)
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/BiologicalLife(seconds, times_fired)
+ ..()
+ if(is_in_host())
+ if(!is_servant_of_ratvar(host))
+ emerge_from_host(FALSE, TRUE)
+ unbind_from_host()
+ return
+ if(!GLOB.ratvar_awakens && host.stat == DEAD)
+ death()
+ return
+ if(GLOB.ratvar_awakens)
+ adjustHealth(-50)
+ else
+ adjustHealth(-10)
+ if(!recovering)
+ heal_host() //also heal our host if inside of them and we aren't recovering
+ else if(health == maxHealth)
+ to_chat(src, "Your strength has returned. You can once again come forward!")
+ to_chat(host, "Your guardian is now strong enough to come forward again!")
+ recovering = FALSE
+ else
+ if(GLOB.ratvar_awakens) //If Ratvar is alive, guardians don't need a host and are downright impossible to kill
+ adjustHealth(-5)
+ heal_host()
+ else if(host)
+ if(!is_servant_of_ratvar(host))
+ unbind_from_host()
+ return
+ if(host.stat == DEAD)
+ adjustHealth(50)
+ to_chat(src, "Your host is dead!")
+ return
+ if(z && host.z && z == host.z)
+ switch(get_dist(get_turf(src), get_turf(host)))
+ if(2)
+ adjustHealth(-1)
+ if(3)
+ //EQUILIBRIUM
+ if(4)
+ adjustHealth(1)
+ if(5)
+ adjustHealth(3)
+ if(6)
+ adjustHealth(6)
+ if(7)
+ adjustHealth(9)
+ if(8 to INFINITY)
+ adjustHealth(15)
+ to_chat(src, "You're too far from your host and rapidly taking damage!")
+ else //right next to or on top of host
+ adjustHealth(-2)
+ heal_host() //gradually heal host if nearby and host is very weak
+ else //well then, you're not even in the same zlevel
+ adjustHealth(15)
+ to_chat(src, "You're too far from your host and rapidly taking damage!")
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/death(gibbed)
+ emerge_from_host(FALSE, TRUE)
+ unbind_from_host()
+ visible_message("[src]'s equipment clatters lifelessly to the ground as the red flames within dissipate.", \
+ "Your equipment falls away. You feel a moment of confusion before your fragile form is annihilated.")
+ . = ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/Stat()
+ ..()
+ if(statpanel("Status"))
+ stat(null, "Current True Name: [true_name]")
+ stat(null, "Host: [host ? host : "NONE"]")
+ if(host)
+ var/resulthealth = round((host.health / host.maxHealth) * 100, 0.5)
+ if(iscarbon(host))
+ resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100)
+ stat(null, "Host Health: [resulthealth]%")
+ if(GLOB.ratvar_awakens)
+ stat(null, "You are [recovering ? "un" : ""]able to deploy!")
+ else
+ if(resulthealth > GUARDIAN_EMERGE_THRESHOLD)
+ stat(null, "You are [recovering ? "unable to deploy" : "able to deploy on hearing your True Name"]!")
+ else
+ stat(null, "You are [recovering ? "unable to deploy" : "able to deploy to protect your host"]!")
+ stat(null, "You do [melee_damage_upper] damage on melee attacks.")
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/Process_Spacemove(movement_dir = 0)
+ return 1
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/bind_to_host(mob/living/new_host)
+ if(!new_host)
+ return FALSE
+ host = new_host
+ var/datum/action/innate/summon_guardian/SG = new()
+ SG.linked_guardian = src
+ SG.Grant(host)
+ var/datum/action/innate/linked_minds/LM = new()
+ LM.linked_guardian = src
+ LM.Grant(host)
+ return TRUE
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/unbind_from_host()
+ if(host)
+ for(var/datum/action/innate/summon_guardian/SG in host.actions)
+ qdel(SG)
+ for(var/datum/action/innate/linked_minds/LM in host.actions)
+ qdel(LM)
+ host = null
+ return TRUE
+ return FALSE
+
+//DAMAGE and FATIGUE
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/heal_host()
+ if(!host)
+ return
+ var/resulthealth = round((host.health / host.maxHealth) * 100, 0.5)
+ if(iscarbon(host))
+ resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100)
+ if(GLOB.ratvar_awakens || resulthealth <= GUARDIAN_EMERGE_THRESHOLD)
+ new /obj/effect/temp_visual/heal(host.loc, "#AF0AAF")
+ host.heal_ordered_damage(4, damage_heal_order)
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
+ if(amount > 0)
+ for(var/mob/living/L in view(2, src))
+ if(L.is_holding_item_of_type(/obj/item/nullrod))
+ to_chat(src, "The presence of a brandished holy artifact weakens your armor!")
+ amount *= 4 //if a wielded null rod is nearby, it takes four times the health damage
+ break
+ . = ..()
+ if(src && updating_health)
+ update_health_hud()
+ update_stats()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/update_health_hud()
+ if(hud_used && hud_used.healths)
+ if(istype(hud_used, /datum/hud/marauder))
+ var/datum/hud/marauder/G = hud_used
+ var/resulthealth
+ if(host)
+ if(iscarbon(host))
+ resulthealth = "[round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100)]%"
+ else
+ resulthealth = "[round((host.health / host.maxHealth) * 100, 0.5)]%"
+ else
+ resulthealth = "NONE"
+ G.hosthealth.maptext = "
HOST [resulthealth]
"
+ hud_used.healths.maptext = "
[round((health / maxHealth) * 100, 0.5)]%"
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/update_stats()
+ if(GLOB.ratvar_awakens)
+ speed = 0
+ melee_damage_lower = 20
+ melee_damage_upper = 20
+ attack_verb_continuous = "devastates"
+ else
+ var/healthpercent = (health/maxHealth) * 100
+ switch(healthpercent)
+ if(100 to 70) //Bonuses to speed and damage at high health
+ speed = 0
+ melee_damage_lower = 16
+ melee_damage_upper = 16
+ attack_verb_continuous = "viciously slashes"
+ if(70 to 40)
+ speed = initial(speed)
+ melee_damage_lower = initial(melee_damage_lower)
+ melee_damage_upper = initial(melee_damage_upper)
+ attack_verb_continuous = initial(attack_verb_continuous)
+ if(40 to 30) //Damage decrease, but not speed
+ speed = initial(speed)
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ attack_verb_continuous = "lightly slashes"
+ if(30 to 20) //Speed decrease
+ speed = 2
+ melee_damage_lower = 8
+ melee_damage_upper = 8
+ attack_verb_continuous = "lightly slashes"
+ if(20 to 10) //Massive speed decrease and weak melee attacks
+ speed = 3
+ melee_damage_lower = 6
+ melee_damage_upper = 6
+ attack_verb_continuous = "weakly slashes"
+ if(10 to 0) //We are super weak and going to die
+ speed = 4
+ melee_damage_lower = 4
+ melee_damage_upper = 4
+ attack_verb_continuous = "taps"
+
+//ATTACKING, BLOCKING, and COUNTERING
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/AttackingTarget()
+ if(is_in_host())
+ return FALSE
+ return ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/bullet_act(obj/item/projectile/Proj)
+ if(blockOrCounter(null, Proj))
+ return
+ return ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/hitby(atom/movable/AM, skipcatch, hitpush, blocked, atom/movable/AM, datum/thrownthing/throwingdatum)
+ if(blockOrCounter(null, AM))
+ return
+ return ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_animal(mob/living/simple_animal/M)
+ if(istype(M, /mob/living/simple_animal/hostile/clockwork/marauder/guardian) || !blockOrCounter(M, M)) //we don't want infinite blockcounter loops if fighting another guardian
+ return ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_paw(mob/living/carbon/monkey/M)
+ if(!blockOrCounter(M, M))
+ return ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_alien(mob/living/carbon/alien/humanoid/M)
+ if(!blockOrCounter(M, M))
+ return ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_slime(mob/living/simple_animal/slime/M)
+ if(!blockOrCounter(M, M))
+ return ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_hand(mob/living/carbon/human/M)
+ if(!blockOrCounter(M, M))
+ return ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/nullrod) || !blockOrCounter(user, I))
+ return ..()
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/blockOrCounter(mob/target, atom/textobject)
+ if(GLOB.ratvar_awakens) //if ratvar has woken, we block nearly everything at a very high chance
+ blockchance = 90
+ counterchance = 90
+ if(prob(blockchance))
+ . = TRUE
+ if(target)
+ target.do_attack_animation(src)
+ target.DelayNextAction(CLICK_CD_MELEE)
+ blockchance = initial(blockchance)
+ playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 30, 1, 0, 1) //clang
+ visible_message("[src] blocks [target && isitem(textobject) ? "[target]'s [textobject.name]":"\the [textobject]"]!", \
+ "You block [target && isitem(textobject) ? "[target]'s [textobject.name]":"\the [textobject]"]!")
+ if(target && Adjacent(target))
+ if(prob(counterchance))
+ counterchance = initial(counterchance)
+ var/previousattack_verb_continuous = attack_verb_continuous
+ attack_verb_continuous = "counters"
+ UnarmedAttack(target)
+ attack_verb_continuous = previousattack_verb_continuous
+ else
+ counterchance = min(counterchance + initial(counterchance), 100)
+ else
+ blockchance = min(blockchance + initial(blockchance), 100)
+ if(GLOB.ratvar_awakens)
+ blockchance = 90
+ counterchance = 90
+
+//COMMUNICATION and EMERGENCE
+/*
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/handle_inherent_channels(message, message_mode)
+ if(host && (is_in_host() || message_mode == MODE_BINARY))
+ guardian_comms(message)
+ return TRUE
+ return ..()
+*/
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/guardian_comms(message)
+ var/name_part = "[src] ([true_name])"
+ message = "\"[message]\"" //Processed output
+ to_chat(src, "[name_part]: [message]")
+ to_chat(host, "[name_part]: [message]")
+ for(var/M in GLOB.mob_list)
+ if(isobserver(M))
+ var/link = FOLLOW_LINK(M, src)
+ to_chat(M, "[link] [name_part] (to[findtextEx(host.name, host.real_name) ? "[host.name]" : "[host.real_name] (as [host.name])"]): [message] ")
+ return TRUE
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/return_to_host()
+ if(is_in_host())
+ return FALSE
+ if(!host)
+ to_chat(src, "You don't have a host!")
+ return FALSE
+ var/resulthealth = round((host.health / host.maxHealth) * 100, 0.5)
+ if(iscarbon(host))
+ resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100)
+ host.visible_message("[host]'s skin flashes crimson!", "You feel [true_name]'s consciousness settle in your mind.")
+ visible_message("[src] suddenly disappears!", "You return to [host].")
+ forceMove(host)
+ if(resulthealth > GUARDIAN_EMERGE_THRESHOLD && health != maxHealth)
+ recovering = TRUE
+ to_chat(src, "You have weakened and will need to recover before manifesting again!")
+ to_chat(host, "[true_name] has weakened and will need to recover before manifesting again!")
+ return TRUE
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/try_emerge()
+ if(!host)
+ to_chat(src, "You don't have a host!")
+ return FALSE
+ if(!GLOB.ratvar_awakens)
+ var/resulthealth = round((host.health / host.maxHealth) * 100, 0.5)
+ if(iscarbon(host))
+ resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100)
+ if(host.stat != DEAD && resulthealth > GUARDIAN_EMERGE_THRESHOLD) //if above 20 health, fails
+ to_chat(src, "Your host must be at [GUARDIAN_EMERGE_THRESHOLD]% or less health to emerge like this!")
+ return FALSE
+ return emerge_from_host(FALSE)
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/emerge_from_host(hostchosen, force) //Notice that this is a proc rather than a verb - guardians can NOT exit at will, but they CAN return
+ if(!is_in_host())
+ return FALSE
+ if(!force && recovering)
+ if(hostchosen)
+ to_chat(host, "[true_name] is too weak to come forth!")
+ else
+ to_chat(host, "[true_name] tries to emerge to protect you, but it's too weak!")
+ to_chat(src, "You try to come forth, but you're too weak!")
+ return FALSE
+ if(!force)
+ if(hostchosen) //guardian approved
+ to_chat(host, "Your words echo with power as [true_name] emerges from your body!")
+ else
+ to_chat(host, "[true_name] emerges from your body to protect you!")
+ forceMove(host.loc)
+ visible_message("[host]'s skin glows red as [name] emerges from their body!", "You exit the safety of [host]'s body!")
+ return TRUE
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/get_alt_name()
+ return " ([text2ratvar(true_name)])"
+
+/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/is_in_host() //Checks if the guardian is inside of their host
+ return host && loc == host
+
+//HOST ACTIONS
+
+//Summon guardian action: Calls forth or recalls your guardian
+/datum/action/innate/summon_guardian
+ name = "Force Guardian to Emerge/Recall"
+ desc = "Allows you to force your clockwork guardian to emerge or recall as required."
+ button_icon_state = "clockwork_marauder"
+ background_icon_state = "bg_clock"
+ check_flags = AB_CHECK_CONSCIOUS
+ buttontooltipstyle = "clockcult"
+ var/mob/living/simple_animal/hostile/clockwork/marauder/guardian/linked_guardian
+ var/list/defend_phrases = list("Defend me", "Come forth", "Assist me", "Protect me", "Give aid", "Help me")
+ var/list/return_phrases = list("Return", "Return to me", "Your job is done", "You have served", "Come back", "Retreat")
+
+/datum/action/innate/summon_guardian/IsAvailable()
+ if(!linked_guardian)
+ return FALSE
+ if(isliving(owner))
+ var/mob/living/L = owner
+ if(!L.can_speak_vocal() || L.stat)
+ return FALSE
+ return ..()
+
+/datum/action/innate/summon_guardian/Activate()
+ if(linked_guardian.is_in_host())
+ clockwork_say(owner, text2ratvar("[pick(defend_phrases)], [linked_guardian.true_name]!"))
+ linked_guardian.emerge_from_host(TRUE)
+ else
+ clockwork_say(owner, text2ratvar("[pick(return_phrases)], [linked_guardian.true_name]!"))
+ linked_guardian.return_to_host()
+ return TRUE
+
+//Linked Minds action: talks to your guardian
+/datum/action/innate/linked_minds
+ name = "Linked Minds"
+ desc = "Allows you to silently communicate with your guardian."
+ button_icon_state = "linked_minds"
+ background_icon_state = "bg_clock"
+ check_flags = AB_CHECK_CONSCIOUS
+ buttontooltipstyle = "clockcult"
+ var/mob/living/simple_animal/hostile/clockwork/marauder/guardian/linked_guardian
+
+/datum/action/innate/linked_minds/IsAvailable()
+ if(!linked_guardian)
+ return FALSE
+ return ..()
+
+/datum/action/innate/linked_minds/Activate()
+ var/message = stripped_input(owner, "Enter a message to tell your guardian.", "Telepathy")
+ if(!owner || !message)
+ return FALSE
+ if(!linked_guardian)
+ to_chat(owner, "Your guardian seems to have been destroyed!")
+ return FALSE
+ var/name_part = "Servant [findtextEx(owner.name, owner.real_name) ? "[owner.name]" : "[owner.real_name] (as [owner.name])"]"
+ message = "\"[message]\"" //Processed output
+ to_chat(owner, "[name_part]: [message]")
+ to_chat(linked_guardian, "[name_part]: [message]")
+ for(var/M in GLOB.mob_list)
+ if(isobserver(M))
+ var/link = FOLLOW_LINK(M, src)
+ to_chat(M, "[link] [name_part] (to[linked_guardian] ([linked_guardian.true_name])): [message]")
+ return TRUE
diff --git a/code/modules/antagonists/clockcult/clock_scripture.dm b/code/modules/antagonists/clockcult/clock_scripture.dm
index 1ebefe4d05..a85245e9d0 100644
--- a/code/modules/antagonists/clockcult/clock_scripture.dm
+++ b/code/modules/antagonists/clockcult/clock_scripture.dm
@@ -3,8 +3,9 @@ Tiers and Requirements
Pieces of scripture require certain follower counts, contruction value, and active caches in order to recite.
Drivers: Unlocked by default
-Scripts: 5 servants and a cache
-Applications: 8 servants, 3 caches, and 100 CV
+Scripts: 35k power or one convert
+Applications: 50k or three converts
+Judgement 5 converts
*/
/datum/clockwork_scripture
@@ -129,11 +130,11 @@ Applications: 8 servants, 3 caches, and 100 CV
SEND_SOUND(invoker, sound('sound/magic/clockwork/invoke_general.ogg'))
return TRUE
-/datum/clockwork_scripture/proc/check_offstation_penalty()
+/datum/clockwork_scripture/proc/check_offstation_penalty()//don't cast spells away from the station
var/turf/T = get_turf(invoker)
if(!T || (!is_centcom_level(T.z) && !is_station_level(T.z) && !is_mining_level(T.z) && !is_reebe(T.z)))
- channel_time *= 2
- power_cost *= 2
+ channel_time *= 3
+ power_cost *= 3
return TRUE
/datum/clockwork_scripture/proc/check_special_requirements() //Special requirements for scriptures, checked multiple times during invocation
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm
index ffe9ecfa80..cbf3bdaa38 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm
@@ -1,5 +1,5 @@
//////////////////
-// APPLICATIONS //
+// APPLICATIONS // For various structures and base building, as well as advanced power generation.
//////////////////
@@ -23,6 +23,37 @@
quickbind = TRUE
quickbind_desc = "Creates a Sigil of Transmission, which can drain and will store power for clockwork structures."
+//Prolonging Prism: Creates a prism that will delay the shuttle at a power cost
+/datum/clockwork_scripture/create_object/prolonging_prism
+ descname = "Powered Structure, Delay Emergency Shuttles"
+ name = "Prolonging Prism"
+ desc = "Creates a mechanized prism which will delay the arrival of an emergency shuttle by 2 minutes at a massive power cost."
+ invocations = list("May this prism...", "...grant us time to enact his will!")
+ channel_time = 80
+ power_cost = 300
+ object_path = /obj/structure/destructible/clockwork/powered/prolonging_prism
+ creator_message = "You form a prolonging prism, which will delay the arrival of an emergency shuttle at a massive power cost."
+ observer_message = "An onyx prism forms in midair and sprouts tendrils to support itself!"
+ invokers_required = 2
+ multiple_invokers_used = TRUE
+ usage_tip = "The power cost to delay a shuttle increases based on the number of times activated."
+ tier = SCRIPTURE_APPLICATION
+ one_per_tile = TRUE
+ primary_component = VANGUARD_COGWHEEL
+ sort_priority = 4
+ important = TRUE
+ quickbind = TRUE
+ quickbind_desc = "Creates a Prolonging Prism, which will delay the arrival of an emergency shuttle by 2 minutes at a massive power cost."
+
+/datum/clockwork_scripture/create_object/prolonging_prism/check_special_requirements()
+ if(SSshuttle.emergency.mode == SHUTTLE_DOCKED || SSshuttle.emergency.mode == SHUTTLE_IGNITING || SSshuttle.emergency.mode == SHUTTLE_STRANDED || SSshuttle.emergency.mode == SHUTTLE_ESCAPE)
+ to_chat(invoker, "\"It is too late to construct one of these, champion.\"")
+ return FALSE
+ var/turf/T = get_turf(invoker)
+ if(!T || !is_station_level(T.z))
+ to_chat(invoker, "\"You must be on the station to construct one of these, champion.\"")
+ return FALSE
+ return ..()
//Mania Motor: Creates a malevolent transmitter that will broadcast the whispers of Sevtug into the minds of nearby nonservants, causing a variety of mental effects at a power cost.
/datum/clockwork_scripture/create_object/mania_motor
@@ -44,6 +75,7 @@
sort_priority = 2
quickbind = TRUE
quickbind_desc = "Creates a Mania Motor, which causes minor damage and negative mental effects in non-Servants."
+ requires_full_power = TRUE
//Clockwork Obelisk: Creates a powerful obelisk that can be used to broadcast messages or open a gateway to any servant or clockwork obelisk at a power cost.
@@ -67,6 +99,64 @@
quickbind = TRUE
quickbind_desc = "Creates a Clockwork Obelisk, which can send messages or open Spatial Gateways with power."
+//Memory Allocation: Finds a willing ghost and makes them into a clockwork guardian for the invoker.
+/datum/clockwork_scripture/memory_allocation
+ descname = "Personal Guardian, A Peice Of Your Mind."
+ name = "Memory Allocation"
+ desc = "Allocates part of your consciousness to a Clockwork Guardian, a variant of Marauder that lives within you, able to be \
+ called forth by Speaking its True Name or if you become exceptionally low on health. \
+ If it remains close to you, you will gradually regain health up to a low amount, but it will die if it goes too far from you."
+ invocations = list("Fright's will...", "...call forth...")
+ channel_time = 100
+ power_cost = 8000
+ usage_tip = "guardians are useful as personal bodyguards and frontline warriors."
+ tier = SCRIPTURE_APPLICATION
+ primary_component = GEIS_CAPACITOR
+ sort_priority = 5
+
+/datum/clockwork_scripture/memory_allocation/check_special_requirements()
+ for(var/mob/living/simple_animal/hostile/clockwork/marauder/guardian/M in GLOB.all_clockwork_mobs)
+ if(M.host == invoker)
+ to_chat(invoker, "You can only house one guardian at a time!")
+ return FALSE
+ return TRUE
+
+/datum/clockwork_scripture/memory_allocation/scripture_effects()
+ return create_guardian()
+
+/datum/clockwork_scripture/memory_allocation/proc/create_guardian()
+ invoker.visible_message("A purple tendril appears from [invoker]'s [slab.name] and impales itself in [invoker.p_their()] forehead!", \
+ "A tendril flies from [slab] into your forehead. You begin waiting while it painfully rearranges your thought pattern...")
+ //invoker.notransform = TRUE //Vulnerable during the process
+ slab.busy = "Thought Modification in progress"
+ if(!do_after(invoker, 50, target = invoker))
+ invoker.visible_message("The tendril, covered in blood, retracts from [invoker]'s head and back into the [slab.name]!", \
+ "Total agony overcomes you as the tendril is forced out early!")
+ invoker.Knockdown(100)
+ invoker.apply_damage(50, BRUTE, "head")//Sevtug leaves a gaping hole in your face if interrupted.
+ slab.busy = null
+ return FALSE
+ clockwork_say(invoker, text2ratvar("...the mind made..."))
+ //invoker.notransform = FALSE
+ slab.busy = "Guardian Selection in progress"
+ if(!check_special_requirements())
+ return FALSE
+ to_chat(invoker, "The tendril shivers slightly as it selects a guardian...")
+ var/list/marauder_candidates = pollGhostCandidates("Do you want to play as the clockwork guardian of [invoker.real_name]?", ROLE_SERVANT_OF_RATVAR, null, FALSE, 50, POLL_IGNORE_HOLOPARASITE)
+ if(!check_special_requirements())
+ return FALSE
+ if(!marauder_candidates.len)
+ invoker.visible_message("The tendril retracts from [invoker]'s head, sealing the entry wound as it does so!", \
+ "The tendril was unsuccessful! Perhaps you should try again another time.")
+ return FALSE
+ clockwork_say(invoker, text2ratvar("...sword and shield!"))
+ var/mob/dead/observer/theghost = pick(marauder_candidates)
+ var/mob/living/simple_animal/hostile/clockwork/marauder/guardian/M = new(invoker)
+ M.key = theghost.key
+ M.bind_to_host(invoker)
+ invoker.visible_message("The tendril retracts from [invoker]'s head, sealing the entry wound as it does so!", \
+ "[M.true_name], a clockwork guardian, has taken up residence in your mind. Communicate with it via the \"Linked Minds\" action button.")
+ return TRUE
//Clockwork Marauder: Creates a construct shell for a clockwork marauder, a well-rounded frontline fighter.
/datum/clockwork_scripture/create_object/construct/clockwork_marauder
@@ -81,7 +171,7 @@
tier = SCRIPTURE_APPLICATION
one_per_tile = TRUE
primary_component = BELLIGERENT_EYE
- sort_priority = 4
+ sort_priority = 6
quickbind = TRUE
quickbind_desc = "Creates a clockwork marauder, used for frontline combat."
object_path = /obj/item/clockwork/construct_chassis/clockwork_marauder
@@ -117,14 +207,13 @@
/datum/clockwork_scripture/create_object/summon_arbiter
descname = "Powerful Assault Mech"
name = "Summon Neovgre, the Anima Bulwark"
- desc = "Calls forth the mighty Anima Bulwark, a weapon of unmatched power,\
- mech with superior defensive and offensive capabilities. It will \
+ desc = "Calls forth the mighty Anima Bulwark, a mech with superior defensive and offensive capabilities. It will \
steadily regenerate HP and triple its regeneration speed while standing \
on a clockwork tile. It will automatically draw power from nearby sigils of \
transmission should the need arise. Its Arbiter laser cannon can decimate foes \
from a range and is capable of smashing through any barrier presented to it. \
- Be warned, choosing to pilot Neovgre is a lifetime commitment, once you are \
- in you cannot leave and when it is destroyed it will explode catastrophically with you inside."
+ Be warned however, choosing to pilot Neovgre is a lifetime commitment, once you are \
+ in you cannot leave and when it is destroyed it will explode catastrophically, with you inside."
invocations = list("By the strength of the alloy...!!", "...call forth the Arbiter!!")
channel_time = 200 // This is a strong fucking weapon, 20 seconds channel time is getting off light I tell ya.
power_cost = 75000 //75 KW
@@ -134,7 +223,7 @@
object_path = /obj/mecha/combat/neovgre
tier = SCRIPTURE_APPLICATION
primary_component = BELLIGERENT_EYE
- sort_priority = 2
+ sort_priority = 7
creator_message = "Neovgre, the Anima Bulwark towers over you... your enemies reckoning has come."
/datum/clockwork_scripture/create_object/summon_arbiter/check_special_requirements()
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_cyborg.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_cyborg.dm
index 819dfac72e..3dacecf6b4 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_cyborg.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_cyborg.dm
@@ -1,5 +1,5 @@
/////////////////
-// CYBORG ONLY //
+// CYBORG ONLY // Cyborgs only, fleshed ones.
/////////////////
//Linked Vanguard: grants Vanguard to the invoker and a target
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm
index 6349ecb581..00c4d73428 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm
@@ -1,5 +1,5 @@
/////////////
-// DRIVERS //
+// DRIVERS // Starter spells
/////////////
//Stargazer: Creates a stargazer, a cheap power generator that utilizes starlight.
@@ -97,7 +97,7 @@
desc = "Charges your slab with divine energy, allowing you to overwhelm a target with Ratvar's light."
invocations = list("Divinity, show them your light!")
whispered = TRUE
- channel_time = 20 // I think making kindle channel a third of the time less is a good make up for the fact that it silences people for such a little amount of time.
+ channel_time = 15 // I think making kindle channel a third of the time less is a good make up for the fact that it silences people for such a little amount of time.
power_cost = 125
usage_tip = "The light can be used from up to two tiles away. Damage taken will GREATLY REDUCE the stun's duration."
tier = SCRIPTURE_DRIVER
@@ -113,7 +113,6 @@
quickbind = TRUE
quickbind_desc = "Stuns and mutes a target from a short range."
-
//Hateful Manacles: Applies restraints from melee over several seconds. The restraints function like handcuffs and break on removal.
/datum/clockwork_scripture/ranged_ability/hateful_manacles
descname = "Handcuffs"
@@ -192,43 +191,39 @@
Click your slab to cancel."
+/*//commenting this out until its reworked to actually do random teleports
//Abscond: Used to return to Reebe.
/datum/clockwork_scripture/abscond
- descname = "Return to Reebe"
+ descname = "Safety warp, teleports you somewhere random. moderately high power cost to use."
name = "Abscond"
- desc = "Yanks you through space, returning you to home base."
+ desc = "Yanks you through space, putting you in hopefully a safe location."
invocations = list("As we bid farewell, and return to the stars...", "...we shall find our way home.")
whispered = TRUE
- channel_time = 50
- power_cost = 5
- special_power_text = "POWERCOST to bring pulled creature"
- special_power_cost = ABSCOND_ABDUCTION_COST
+ channel_time = 3.5
+ power_cost = 10000
usage_tip = "This can't be used while on Reebe, for obvious reasons."
tier = SCRIPTURE_DRIVER
primary_component = GEIS_CAPACITOR
sort_priority = 9
important = TRUE
quickbind = TRUE
- quickbind_desc = "Returns you to Reebe."
+ quickbind_desc = "Teleports you somewhere random, or to an active Ark if one exists. Use in emergencies."
var/client_color
requires_full_power = TRUE
/datum/clockwork_scripture/abscond/check_special_requirements()
if(is_reebe(invoker.z))
- to_chat(invoker, "You're already at Reebe.")
+ to_chat(invoker, "You're at Reebe, attempting to warp in the void could cause you to share your masters fate of banishment!.")
return
if(!isturf(invoker.loc))
- to_chat(invoker, "You must be visible to return!")
+ to_chat(invoker, "You must be visible to warp!")
return
return TRUE
/datum/clockwork_scripture/abscond/recital()
- client_color = invoker.client.color
- animate(invoker.client, color = "#AF0AAF", time = 50)
. = ..()
/datum/clockwork_scripture/abscond/scripture_effects()
- var/mob/living/pulled_mob = (invoker.pulling && isliving(invoker.pulling) && get_clockwork_power(ABSCOND_ABDUCTION_COST)) ? invoker.pulling : null
var/turf/T
if(GLOB.ark_of_the_clockwork_justiciar)
T = get_step(GLOB.ark_of_the_clockwork_justiciar, SOUTH)
@@ -237,21 +232,12 @@
if(!do_teleport(invoker, T, channel = TELEPORT_CHANNEL_CULT, forced = TRUE))
return
invoker.visible_message("[invoker] flickers and phases out of existence!", \
- "You feel a dizzying sense of vertigo as you're yanked back to Reebe!")
+ "You feel a dizzying sense of vertigo as you're yanked through the fabric of reality!")
T.visible_message("[invoker] flickers and phases into existence!")
playsound(invoker, 'sound/magic/magic_missile.ogg', 50, TRUE)
playsound(T, 'sound/magic/magic_missile.ogg', 50, TRUE)
do_sparks(5, TRUE, invoker)
- do_sparks(5, TRUE, T)
- if(pulled_mob && do_teleport(pulled_mob, T, channel = TELEPORT_CHANNEL_CULT, forced = TRUE))
- adjust_clockwork_power(-special_power_cost)
- invoker.start_pulling(pulled_mob) //forcemove resets pulls, so we need to re-pull
- if(invoker.client)
- animate(invoker.client, color = client_color, time = 25)
-
-/datum/clockwork_scripture/abscond/scripture_fail()
- if(invoker && invoker.client)
- animate(invoker.client, color = client_color, time = 10)
+ do_sparks(5, TRUE, T)*/
//Replicant: Creates a new clockwork slab.
@@ -265,7 +251,7 @@
whispered = TRUE
object_path = /obj/item/clockwork/slab
creator_message = "You copy a piece of replicant alloy and command it into a new slab."
- usage_tip = "This is inefficient as a way to produce components, as the slab produced must be held by someone with no other slabs to produce components."
+ usage_tip = "This is inefficient as a way to produce power, as the slab produced must be held by someone with no other slabs to produce any."
tier = SCRIPTURE_DRIVER
space_allowed = TRUE
primary_component = GEIS_CAPACITOR
@@ -293,3 +279,50 @@
sort_priority = 11
quickbind = TRUE
quickbind_desc = "Creates a pair of Wraith Spectacles, which grant true sight but cause gradual vision loss."
+
+//Spatial Gateway: Allows the invoker to teleport themselves and any nearby allies to a conscious servant or clockwork obelisk.
+/datum/clockwork_scripture/spatial_gateway
+ descname = "Teleport Gate"
+ name = "Spatial Gateway"
+ desc = "Tears open a miniaturized gateway in spacetime to any conscious servant that can transport objects or creatures to its destination. \
+ Each servant assisting in the invocation adds one additional use and four additional seconds to the gateway's uses and duration."
+ invocations = list("Spatial Gateway...", "...activate!")
+ channel_time = 30
+ power_cost = 400
+ whispered = TRUE
+ multiple_invokers_used = TRUE
+ multiple_invokers_optional = TRUE
+ usage_tip = "This gateway is strictly one-way and will only allow things through the invoker's portal."
+ tier = SCRIPTURE_DRIVER
+ primary_component = GEIS_CAPACITOR
+ sort_priority = 9
+ quickbind = TRUE
+ quickbind_desc = "Allows you to create a one-way Spatial Gateway to a living Servant or Clockwork Obelisk."
+
+/datum/clockwork_scripture/spatial_gateway/check_special_requirements()
+ if(!isturf(invoker.loc))
+ to_chat(invoker, "You must not be inside an object to use this scripture!")
+ return FALSE
+ var/other_servants = 0
+ for(var/mob/living/L in GLOB.alive_mob_list)
+ if(is_servant_of_ratvar(L) && !L.stat && L != invoker)
+ other_servants++
+ for(var/obj/structure/destructible/clockwork/powered/clockwork_obelisk/O in GLOB.all_clockwork_objects)
+ if(O.anchored)
+ other_servants++
+ if(!other_servants)
+ to_chat(invoker, "There are no other conscious servants or anchored clockwork obelisks!")
+ return FALSE
+ return TRUE
+
+/datum/clockwork_scripture/spatial_gateway/scripture_effects()
+ var/portal_uses = 0
+ var/duration = 0
+ for(var/mob/living/L in range(1, invoker))
+ if(!L.stat && is_servant_of_ratvar(L))
+ portal_uses++
+ duration += 40 //4 seconds
+ if(GLOB.ratvar_awakens)
+ portal_uses = max(portal_uses, 100) //Very powerful if Ratvar has been summoned
+ duration = max(duration, 100)
+ return slab.procure_gateway(invoker, duration, portal_uses)
\ No newline at end of file
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_judgement.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_judgement.dm
new file mode 100644
index 0000000000..5075840e76
--- /dev/null
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_judgement.dm
@@ -0,0 +1,44 @@
+///////////////
+// JUDGEMENT // For the big game changing things. TODO: Summonable generals, just need mob sprites for them.
+///////////////
+
+//Ark of the Clockwork Justiciar: Creates a Gateway to the Celestial Derelict, summoning ratvar.
+/datum/clockwork_scripture/create_object/ark_of_the_clockwork_justiciar
+ descname = "Structure, Win Condition"
+ name = "Ark of the Clockwork Justiciar"
+ desc = "Tears apart a rift in spacetime to Reebe, the Celestial Derelict, using a massive amount of power.\n\
+ This gateway will, after some time, call forth Ratvar from his exile and massively empower all scriptures and tools."
+ invocations = list("ARMORER! FRIGHT! AMPERAGE! VANGUARD! WE CALL UPON YOU!!", \
+ "THE TIME HAS COME FOR OUR MASTER TO BREAK THE CHAINS OF EXILE!!", \
+ "LEND US YOUR AID! ENGINE COMES!!")
+ channel_time = 150
+ power_cost = 70000 //70 KW. It's literally the thing wrenching the god out of another dimension why wouldn't it be costly.
+ invokers_required = 6
+ multiple_invokers_used = TRUE
+ object_path = /obj/structure/destructible/clockwork/massive/celestial_gateway
+ creator_message = "The Ark swirls into existance before you with the help of the Generals. After all this time, he shall, finally, be free"
+ usage_tip = "The gateway is completely vulnerable to attack during its five-minute duration. It will periodically give indication of its general position to everyone on the station \
+ as well as being loud enough to be heard throughout the entire sector. Defend it with your life!"
+ tier = SCRIPTURE_APPLICATION
+ sort_priority = 8
+ requires_full_power = TRUE
+
+/datum/clockwork_scripture/create_object/ark_of_the_clockwork_justiciar/check_special_requirements()
+ if(!slab.no_cost)
+ if(GLOB.ratvar_awakens)
+ to_chat(invoker, "\"I am already here, there is no point in that.\"")
+ return FALSE
+ for(var/obj/structure/destructible/clockwork/massive/celestial_gateway/G in GLOB.all_clockwork_objects)
+ var/area/gate_area = get_area(G)
+ to_chat(invoker, "There is already an Ark at [gate_area.map_name]!")
+ return FALSE
+ var/area/A = get_area(invoker)
+ var/turf/T = get_turf(invoker)
+ if(!T || !is_station_level(T.z) || istype(A, /area/shuttle) || !A.blob_allowed)
+ to_chat(invoker, "You must be on the station to activate the Ark!")
+ return FALSE
+ if(GLOB.clockwork_gateway_activated)
+ to_chat(invoker, "Ratvar's recent banishment renders him too weak to be wrung forth from Reebe!")
+ return FALSE
+ return ..()
+
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm
index d22a2f69b7..8819544928 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm
@@ -1,5 +1,5 @@
/////////////
-// SCRIPTS //
+// SCRIPTS // Various miscellanious spells for offense/defense/construction.
/////////////
@@ -81,6 +81,25 @@
return /obj/effect/clockwork/sigil/vitality/neutered
return ..()
+//Sigil of Rites: Creates a sigil that allows to perform certain rites on it. More information on these can be found in clock_rites.dm, they usually require power, materials and sometimes a target.
+/datum/clockwork_scripture/create_object/sigil_of_rites
+ descname = "Sigil, Access to rites"
+ name = "Sigil of Rites"
+ desc = "Places a sigil that, when interacted with, will allow for a variety of rites to be performed on the sigil. These usually require power cells, clockwork power, and some other components."
+ invocations = list("Engine, allow us..", ".. to be blessed with your rites.")
+ channel_time = 80
+ power_cost = 1400
+ invokers_required = 2
+ multiple_invokers_used = TRUE
+ whispered = TRUE
+ object_path = /obj/effect/clockwork/sigil/rite
+ creator_message = "A sigil of Rites appears beneath you. It will allow you to perform certain rites, given sufficient materials and power."
+ usage_tip = "It may be useful to coordinate to acquire needed materials quickly."
+ tier = SCRIPTURE_SCRIPT
+ one_per_tile = TRUE
+ primary_component = HIEROPHANT_ANSIBLE
+ sort_priority = 4
+
//Judicial Visor: Creates a judicial visor, which can smite an area.
/datum/clockwork_scripture/create_object/judicial_visor
descname = "Delayed Area Knockdown Glasses"
@@ -96,7 +115,7 @@
tier = SCRIPTURE_SCRIPT
space_allowed = TRUE
primary_component = BELLIGERENT_EYE
- sort_priority = 4
+ sort_priority = 5
quickbind = TRUE
quickbind_desc = "Creates a Judicial Visor, which can smite an area, applying Belligerent and briefly stunning."
@@ -115,7 +134,7 @@
tier = SCRIPTURE_SCRIPT
space_allowed = TRUE
primary_component = VANGUARD_COGWHEEL
- sort_priority = 6
+ sort_priority = 7
quickbind = TRUE
quickbind_desc = "Creates a Ratvarian shield, which can absorb energy from attacks for use in powerful bashes."
@@ -131,7 +150,7 @@
usage_tip = "Throwing the spear at a mob will do massive damage and knock them down, but break the spear. You will need to wait for 30 seconds before resummoning it."
tier = SCRIPTURE_SCRIPT
primary_component = VANGUARD_COGWHEEL
- sort_priority = 7
+ sort_priority = 8
important = TRUE
quickbind = TRUE
quickbind_desc = "Permanently binds clockwork armor and a Ratvarian spear to you."
@@ -217,53 +236,6 @@
weapon_type = /obj/item/clockwork/weapon/ratvarian_spear
-//Spatial Gateway: Allows the invoker to teleport themselves and any nearby allies to a conscious servant or clockwork obelisk.
-/datum/clockwork_scripture/spatial_gateway
- descname = "Teleport Gate"
- name = "Spatial Gateway"
- desc = "Tears open a miniaturized gateway in spacetime to any conscious servant that can transport objects or creatures to its destination. \
- Each servant assisting in the invocation adds one additional use and four additional seconds to the gateway's uses and duration."
- invocations = list("Spatial Gateway...", "...activate!")
- channel_time = 80
- power_cost = 400
- multiple_invokers_used = TRUE
- multiple_invokers_optional = TRUE
- usage_tip = "This gateway is strictly one-way and will only allow things through the invoker's portal."
- tier = SCRIPTURE_SCRIPT
- primary_component = GEIS_CAPACITOR
- sort_priority = 9
- quickbind = TRUE
- quickbind_desc = "Allows you to create a one-way Spatial Gateway to a living Servant or Clockwork Obelisk."
-
-/datum/clockwork_scripture/spatial_gateway/check_special_requirements()
- if(!isturf(invoker.loc))
- to_chat(invoker, "You must not be inside an object to use this scripture!")
- return FALSE
- var/other_servants = 0
- for(var/mob/living/L in GLOB.alive_mob_list)
- if(is_servant_of_ratvar(L) && !L.stat && L != invoker)
- other_servants++
- for(var/obj/structure/destructible/clockwork/powered/clockwork_obelisk/O in GLOB.all_clockwork_objects)
- if(O.anchored)
- other_servants++
- if(!other_servants)
- to_chat(invoker, "There are no other conscious servants or anchored clockwork obelisks!")
- return FALSE
- return TRUE
-
-/datum/clockwork_scripture/spatial_gateway/scripture_effects()
- var/portal_uses = 0
- var/duration = 0
- for(var/mob/living/L in range(1, invoker))
- if(!L.stat && is_servant_of_ratvar(L))
- portal_uses++
- duration += 40 //4 seconds
- if(GLOB.ratvar_awakens)
- portal_uses = max(portal_uses, 100) //Very powerful if Ratvar has been summoned
- duration = max(duration, 100)
- return slab.procure_gateway(invoker, duration, portal_uses)
-
-
//Mending Mantra: Channeled for up to ten times over twenty seconds to repair structures and heal allies
/datum/clockwork_scripture/channeled/mending_mantra
descname = "Channeled, Area Healing and Repair"
@@ -276,7 +248,7 @@
usage_tip = "This is a very effective way to rapidly reinforce a base after an attack."
tier = SCRIPTURE_SCRIPT
primary_component = VANGUARD_COGWHEEL
- sort_priority = 8
+ sort_priority = 9
quickbind = TRUE
quickbind_desc = "Repairs nearby structures and constructs. Servants wearing clockwork armor will also be healed. Maximum 10 chants."
var/heal_attempts = 4
@@ -389,7 +361,7 @@
usage_tip = "Though it requires you to stand still, this scripture can do massive damage."
tier = SCRIPTURE_SCRIPT
primary_component = BELLIGERENT_EYE
- sort_priority = 5
+ sort_priority = 6
quickbind = TRUE
quickbind_desc = "Allows you to fire energy rays at target locations. Maximum 5 chants."
var/static/list/nzcrentr_insults = list("You're not very good at aiming.", "You hunt badly.", "What a waste of energy.", "Almost funny to watch.",
@@ -438,7 +410,7 @@
usage_tip = "It may be useful to end channelling early if the burning becomes too much to handle.."
tier = SCRIPTURE_SCRIPT
primary_component = GEIS_CAPACITOR
- sort_priority = 10
+ sort_priority = 11
quickbind = TRUE
quickbind_desc = "Quickly drains power in an area around the invoker, causing burns proportional to the amount of energy drained. Maximum of 20 chants."
@@ -487,3 +459,26 @@
invoker.light_range = 0
invoker.update_light()
return ..()
+
+//Belligerent: Channeled for up to fifteen times over thirty seconds. Forces non-servants that can hear the chant to walk, doing minor damage. Nar-Sian cultists are burned.
+/datum/clockwork_scripture/channeled/belligerent
+ descname = "Channeled, Area Slowdown"
+ name = "Belligerent"
+ desc = "Forces all nearby non-servants to walk rather than run, doing minor damage. Chanted every two seconds for up to thirty seconds."
+ chant_invocations = list("Punish their blindness!", "Take time, make slow!", "Kneel before The Justiciar!", "Halt their charges!", "Cease the tides!")
+ chant_amount = 15
+ chant_interval = 20
+ channel_time = 20
+ power_cost = 300
+ usage_tip = "Useful for crowd control in a populated area and disrupting mass movement."
+ tier = SCRIPTURE_DRIVER
+ primary_component = BELLIGERENT_EYE
+ sort_priority = 1
+ quickbind = TRUE
+ quickbind_desc = "Forces nearby non-Servants to walk, doing minor damage with each chant. Maximum 15 chants."
+
+/datum/clockwork_scripture/channeled/belligerent/chant_effects(chant_number)
+ for(var/mob/living/carbon/C in hearers(7, invoker))
+ C.apply_status_effect(STATUS_EFFECT_BELLIGERENT)
+ new /obj/effect/temp_visual/ratvar/belligerent(get_turf(invoker))
+ return TRUE
diff --git a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
index 297856f531..7478d45b08 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
@@ -1,5 +1,3 @@
-#define ARK_GRACE_PERIOD 300 //In seconds, how long the crew has before the Ark truly "begins"
-
/proc/clockwork_ark_active() //A helper proc so the Ark doesn't have to be typecast every time it's checked; returns null if there is no Ark and its active var otherwise
var/obj/structure/destructible/clockwork/massive/celestial_gateway/G = GLOB.ark_of_the_clockwork_justiciar
if(!G)
@@ -11,7 +9,7 @@
name = "\improper Ark of the Clockwork Justicar"
desc = "A massive, hulking amalgamation of parts. It seems to be maintaining a very unstable bluespace anomaly."
clockwork_desc = "Nezbere's magnum opus: a hulking clockwork machine capable of combining bluespace and steam power to summon Ratvar. Once activated, \
- its instability will cause one-way bluespace rifts to open across the station to the City of Cogs, so be prepared to defend it at all costs."
+ its instability will alert the entire area, so be prepared to defend it at all costs."
max_integrity = 500
mouse_opacity = MOUSE_OPACITY_OPAQUE
icon = 'icons/effects/clockwork_effects.dmi'
@@ -22,9 +20,8 @@
immune_to_servant_attacks = TRUE
var/active = FALSE
var/progress_in_seconds = 0 //Once this reaches GATEWAY_RATVAR_ARRIVAL, it's game over
- var/grace_period = ARK_GRACE_PERIOD //This exists to allow the crew to gear up and prepare for the invasion
- var/initial_activation_delay = -1 //How many seconds the Ark will have initially taken to activate
- var/seconds_until_activation = -1 //How many seconds until the Ark activates; if it should never activate, set this to -1
+ var/initial_activation_delay = 5 //How many seconds the Ark will have initially taken to activate
+ var/seconds_until_activation = 5 //How many seconds until the Ark activates; if it should never activate, set this to -1
var/purpose_fulfilled = FALSE
var/first_sound_played = FALSE
var/second_sound_played = FALSE
@@ -38,10 +35,21 @@
/obj/structure/destructible/clockwork/massive/celestial_gateway/Initialize()
. = ..()
+ INVOKE_ASYNC(src, .proc/spawn_animation)
glow = new(get_turf(src))
if(!GLOB.ark_of_the_clockwork_justiciar)
GLOB.ark_of_the_clockwork_justiciar = src
- START_PROCESSING(SSprocessing, src)
+
+/obj/structure/destructible/clockwork/massive/celestial_gateway/on_attack_hand(mob/user, act_intent, unarmed_attack_flags)
+ if(!active && is_servant_of_ratvar(user) && user.canUseTopic(src, !issilicon(user), NO_DEXTERY))
+ if(alert(user, "Are you sure you want to activate the ark? Once enabled, there will be no turning back.", "Enabling the ark", "Activate!", "Cancel") == "Activate!")
+ if(active)
+ return
+ log_game("[key_name(user)] has activated an Ark of the Clockwork Justicar at [COORD(src)].")
+ START_PROCESSING(SSprocessing, src)
+ SSshuttle.registerHostileEnvironment(src)
+ else
+ to_chat(user, "You decide against activating the ark.. for now.")
/obj/structure/destructible/clockwork/massive/celestial_gateway/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
. = ..()
@@ -49,7 +57,7 @@
flick("clockwork_gateway_damaged", glow)
playsound(src, 'sound/machines/clockcult/ark_damage.ogg', 75, FALSE)
if(last_scream < world.time)
- audible_message("An unearthly screaming sound resonates throughout Reebe!")
+ audible_message("An unearthly screaming sound resonates throughout the area!")
for(var/V in GLOB.player_list)
var/mob/M = V
var/turf/T = get_turf(M)
@@ -60,31 +68,19 @@
/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/final_countdown(ark_time) //WE'RE LEAVING TOGETHEEEEEEEEER
if(!ark_time)
- ark_time = 30 //minutes
- initial_activation_delay = ark_time * 60
- seconds_until_activation = ark_time * 60 //60 seconds in a minute * number of minutes
+ ark_time = 5 //5 minutes
for(var/obj/item/clockwork/construct_chassis/cogscarab/C in GLOB.all_clockwork_objects)
C.infinite_resources = FALSE
GLOB.servants_active = TRUE
SSshuttle.registerHostileEnvironment(src)
-/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/cry_havoc()
- visible_message("[src] shudders and roars to life, its parts beginning to whirr and screech!")
- hierophant_message("The Ark is activating! You will be transported there soon!")
- for(var/mob/M in GLOB.player_list)
- var/turf/T = get_turf(M)
- if(is_servant_of_ratvar(M) || isobserver(M) || (T && T.z == z))
- M.playsound_local(M, 'sound/magic/clockwork/ark_activation_sequence.ogg', 30, FALSE, pressure_affected = FALSE)
- addtimer(CALLBACK(src, .proc/let_slip_the_dogs), 300)
-
/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/let_slip_the_dogs()
- spawn_animation()
first_sound_played = TRUE
active = TRUE
+ visible_message("[src] shudders and roars to life, its parts beginning to whirr and screech!")
priority_announce("Massive [Gibberish("bluespace", 100)] anomaly detected on all frequencies. All crew are directed to \
@!$, [text2ratvar("PURGE ALL UNTRUTHS")] <&. the anomalies and destroy their source to prevent further damage to corporate property. This is \
- not a drill.[grace_period ? " Estimated time of appearance: [grace_period] seconds. Use this time to prepare for an attack on [station_name()]." : ""]", \
- "Central Command Higher Dimensional Affairs", 'sound/magic/clockwork/ark_activation.ogg')
+ not a drill.", "Central Command Higher Dimensional Affairs", 'sound/magic/clockwork/ark_activation_sequence.ogg')
set_security_level("delta")
for(var/V in SSticker.mode.servants_of_ratvar)
var/datum/mind/M = V
@@ -92,15 +88,6 @@
continue
if(ishuman(M.current))
M.current.add_overlay(mutable_appearance('icons/effects/genetics.dmi', "servitude", -MUTATIONS_LAYER))
- for(var/V in GLOB.brass_recipes)
- var/datum/stack_recipe/R = V
- if(!R)
- continue
- if(R.title == "wall gear")
- R.time *= 2 //Building walls becomes slower when the Ark activates
- mass_recall()
- recalls_remaining++ //So it doesn't use up a charge
-
var/turf/T = get_turf(src)
var/list/open_turfs = list()
for(var/turf/open/OT in orange(1, T))
@@ -110,14 +97,35 @@
for(var/mob/living/L in T)
L.forceMove(pick(open_turfs))
-/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/open_portal(turf/T)
- new/obj/effect/clockwork/city_of_cogs_rift(T)
-
/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/spawn_animation()
- hierophant_message("The Ark has activated! [grace_period ? "You have [round(grace_period / 60)] minutes until the crew invades! " : ""]Defend it at all costs!", FALSE, src)
- sound_to_playing_players(volume = 10, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
- seconds_until_activation = 0
- SSshuttle.registerHostileEnvironment(src)
+ var/turf/T = get_turf(src)
+ new/obj/effect/clockwork/general_marker/inathneq(T)
+ hierophant_message("\"[text2ratvar("Engine, come forth and show your servants your mercy")]!\"")
+ playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 30, 0)
+ sleep(10)
+ new/obj/effect/clockwork/general_marker/sevtug(T)
+ hierophant_message("\"[text2ratvar("Engine, come forth and show this station your decorating skills")]!\"")
+ playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 45, 0)
+ sleep(10)
+ new/obj/effect/clockwork/general_marker/nezbere(T)
+ hierophant_message("\"[text2ratvar("Engine, come forth and shine your light across this realm")]!!\"")
+ playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 60, 0)
+ sleep(10)
+ new/obj/effect/clockwork/general_marker/nzcrentr(T)
+ hierophant_message("\"[text2ratvar("Engine, come forth")].\"")
+ playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 75, 0)
+ sleep(10)
+ playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 100, 0)
+ var/list/open_turfs = list()
+ for(var/turf/open/OT in orange(1, T))
+ if(!is_blocked_turf(OT, TRUE))
+ open_turfs |= OT
+ if(open_turfs.len)
+ for(var/mob/living/L in T)
+ L.forceMove(pick(open_turfs))
+ glow = new(get_turf(src))
+ var/area/gate_area = get_area(src)
+ hierophant_message("An Ark of the Clockwork Justicar has been created in [gate_area.map_name]!", FALSE, src)
/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/initiate_mass_recall()
recalling = TRUE
@@ -141,35 +149,22 @@
transform = matrix() * 2
animate(src, transform = matrix() * 0.5, time = 30, flags = ANIMATION_END_NOW)
-/obj/structure/destructible/clockwork/massive/celestial_gateway/Destroy()
+obj/structure/destructible/clockwork/massive/celestial_gateway/Destroy()
STOP_PROCESSING(SSprocessing, src)
+ if(!purpose_fulfilled)
+ var/area/gate_area = get_area(src)
+ hierophant_message("An Ark of the Clockwork Justicar has fallen at [gate_area.map_name]!")
+ send_to_playing_players(sound(null, 0, channel = CHANNEL_JUSTICAR_ARK))
+ var/was_stranded = SSshuttle.emergency.mode == SHUTTLE_STRANDED
SSshuttle.clearHostileEnvironment(src)
- if(!purpose_fulfilled && istype(SSticker.mode, /datum/game_mode/clockwork_cult))
- hierophant_message("The Ark has fallen!")
- sound_to_playing_players(null, channel = CHANNEL_JUSTICAR_ARK)
- SSticker.force_ending = TRUE //rip
+ if(!was_stranded && !purpose_fulfilled)
+ priority_announce("Massive energy anomaly no longer on short-range scanners, bluespace distortions still detected.","Central Command Higher Dimensional Affairs")
if(glow)
qdel(glow)
glow = null
if(countdown)
qdel(countdown)
countdown = null
- for(var/mob/L in GLOB.player_list)
- var/turf/T = get_turf(L)
- if(T && T.z == z)
- var/atom/movable/target = L
- if(isobj(L.loc))
- target = L.loc
- target.forceMove(get_turf(pick(GLOB.generic_event_spawns)))
- L.overlay_fullscreen("flash", /obj/screen/fullscreen/flash/static)
- L.clear_fullscreen("flash", 30)
- if(isliving(L))
- var/mob/living/LI = L
- LI.Stun(50)
- for(var/obj/effect/clockwork/city_of_cogs_rift/R in GLOB.all_clockwork_objects)
- qdel(R)
- if(GLOB.ark_of_the_clockwork_justiciar == src)
- GLOB.ark_of_the_clockwork_justiciar = null
. = ..()
/obj/structure/destructible/clockwork/massive/celestial_gateway/deconstruct(disassembled = TRUE)
@@ -203,8 +198,6 @@
/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/get_arrival_time(var/deciseconds = TRUE)
if(seconds_until_activation)
. = seconds_until_activation
- else if(grace_period)
- . = grace_period
else if(GATEWAY_RATVAR_ARRIVAL - progress_in_seconds > 0)
. = round(max((GATEWAY_RATVAR_ARRIVAL - progress_in_seconds) / (GATEWAY_SUMMON_RATE), 0), 1)
if(deciseconds)
@@ -213,8 +206,6 @@
/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/get_arrival_text(s_on_time)
if(seconds_until_activation)
return "[get_arrival_time()][s_on_time ? "S" : ""]"
- if(grace_period)
- return "[get_arrival_time()][s_on_time ? "S" : ""]"
. = "IMMINENT"
if(!obj_integrity)
. = "DETONATING"
@@ -229,17 +220,14 @@
if(!active)
. += "Time until the Ark's activation: [DisplayTimeText(get_arrival_time())]"
else
- if(grace_period)
- . += "Crew grace period time remaining: [DisplayTimeText(get_arrival_time())]"
- else
- . += "Time until Ratvar's arrival: [DisplayTimeText(get_arrival_time())]"
- switch(progress_in_seconds)
- if(-INFINITY to GATEWAY_REEBE_FOUND)
- . += "The Ark is feeding power into the bluespace field."
- if(GATEWAY_REEBE_FOUND to GATEWAY_RATVAR_COMING)
- . += "The field is ripping open a copy of itself in Ratvar's prison."
- if(GATEWAY_RATVAR_COMING to INFINITY)
- . += "With the bluespace field established, Ratvar is preparing to come through!"
+ . += "Time until Ratvar's arrival: [DisplayTimeText(get_arrival_time())]"
+ switch(progress_in_seconds)
+ if(-INFINITY to GATEWAY_REEBE_FOUND)
+ . += "The Ark is feeding power into the bluespace field."
+ if(GATEWAY_REEBE_FOUND to GATEWAY_RATVAR_COMING)
+ . += "The field is ripping open a copy of itself in Ratvar's prison."
+ if(GATEWAY_RATVAR_COMING to INFINITY)
+ . += "With the bluespace field established, Ratvar is preparing to come through!"
else
if(!active)
. += "Whatever it is, it doesn't seem to be active."
@@ -253,20 +241,14 @@
. += "The anomaly is stable! Something is coming through!"
/obj/structure/destructible/clockwork/massive/celestial_gateway/process()
- if(seconds_until_activation == -1) //we never do anything
- return
adjust_clockwork_power(2.5) //Provides weak power generation on its own
if(seconds_until_activation)
if(!countdown)
countdown = new(src)
countdown.start()
seconds_until_activation--
- if(!GLOB.script_scripture_unlocked && initial_activation_delay * 0.5 > seconds_until_activation)
- GLOB.script_scripture_unlocked = TRUE
- hierophant_message("The Ark is halfway prepared. Script scripture is now available!")
if(!seconds_until_activation)
- cry_havoc()
- seconds_until_activation = -1 //we'll set this after cry_havoc()
+ let_slip_the_dogs()
return
if(!first_sound_played || prob(7))
for(var/mob/M in GLOB.player_list)
@@ -285,6 +267,9 @@
if(!step_away(O, src, 2) || get_dist(O, src) < 2)
O.take_damage(50, BURN, "bomb")
O.update_icon()
+
+ conversion_pulse() //Converts the nearby area into clockcult-style
+
for(var/V in GLOB.player_list)
var/mob/M = V
var/turf/T = get_turf(M)
@@ -292,29 +277,24 @@
M.forceMove(get_step(src, SOUTH))
M.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
M.clear_fullscreen("flash", 5)
- if(grace_period)
- grace_period--
- return
progress_in_seconds += GATEWAY_SUMMON_RATE
switch(progress_in_seconds)
if(-INFINITY to GATEWAY_REEBE_FOUND)
if(!second_sound_played)
- for(var/V in GLOB.generic_event_spawns)
- addtimer(CALLBACK(src, .proc/open_portal, get_turf(V)), rand(100, 600))
sound_to_playing_players('sound/magic/clockwork/invoke_general.ogg', 30, FALSE)
- sound_to_playing_players(volume = 20, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
+ sound_to_playing_players(volume = 10, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
second_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_charging"
if(GATEWAY_REEBE_FOUND to GATEWAY_RATVAR_COMING)
if(!third_sound_played)
- sound_to_playing_players(volume = 25, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_active.ogg', TRUE))
+ sound_to_playing_players(volume = 30, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_active.ogg', TRUE))
third_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_active"
if(GATEWAY_RATVAR_COMING to GATEWAY_RATVAR_ARRIVAL)
if(!fourth_sound_played)
- sound_to_playing_players(volume = 30, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_closing.ogg', TRUE))
+ sound_to_playing_players(volume = 70, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_closing.ogg', TRUE))
fourth_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_closing"
@@ -334,7 +314,6 @@
GLOB.clockwork_gateway_activated = TRUE
var/turf/T = SSmapping.get_station_center()
new /obj/structure/destructible/clockwork/massive/ratvar(T)
- SSticker.force_ending = TRUE
var/x0 = T.x
var/y0 = T.y
for(var/I in spiral_range_turfs(255, T, tick_checked = TRUE))
@@ -349,6 +328,17 @@
T.ratvar_act(dist)
CHECK_TICK
+//Converts nearby turfs into their clockwork equivalent, with ever-increasing range the closer the ark is to summoning Ratvar
+/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/conversion_pulse()
+ var/convert_dist = 1 + (round(FLOOR(progress_in_seconds, 15) * 0.067))
+ for(var/t in RANGE_TURFS(convert_dist, loc))
+ var/turf/T = t
+ if(!T)
+ continue
+ var/dist = cheap_hypotenuse(T.x, T.y, x, y)
+ if(dist < convert_dist)
+ T.ratvar_act(FALSE, TRUE, 3)
+
//ATTACK GHOST IGNORING PARENT RETURN VALUE
/obj/structure/destructible/clockwork/massive/celestial_gateway/attack_ghost(mob/user)
if(!IsAdminGhost(user))
@@ -361,9 +351,9 @@
if(alert(user, "You're REALLY SURE? This cannot be undone.", name, "Yes - Activate the Ark", "No") == "Yes - Activate the Ark")
message_admins("Admin [key_name_admin(user)] started the Ark's countdown!")
log_admin("Admin [key_name(user)] started the Ark's countdown on a non-clockcult mode!")
- to_chat(user, "The gamemode is now being treated as clockwork cult, and the Ark is counting down from 30 \
+ to_chat(user, "The gamemode is now being treated as clockwork cult, and the Ark is counting down from 5 \
minutes. You will need to create servant players yourself.")
- final_countdown(35)
+ final_countdown(5)
diff --git a/code/modules/antagonists/clockcult/clock_structures/clockwork_obelisk.dm b/code/modules/antagonists/clockcult/clock_structures/clockwork_obelisk.dm
index 058bd9d24e..2b4b797b4d 100644
--- a/code/modules/antagonists/clockcult/clock_structures/clockwork_obelisk.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/clockwork_obelisk.dm
@@ -41,14 +41,19 @@
affected += try_use_power(MIN_CLOCKCULT_POWER*4)
return affected
-/obj/structure/destructible/clockwork/powered/clockwork_obelisk/attack_hand(mob/living/user)
+/obj/structure/destructible/clockwork/powered/clockwork_obelisk/Destroy()
+ for(var/obj/effect/clockwork/spatial_gateway/SG in loc)
+ SG.ex_act(EXPLODE_DEVASTATE)
+ return ..()
+
+/obj/structure/destructible/clockwork/powered/clockwork_obelisk/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
if(!is_servant_of_ratvar(user) || !can_access_clockwork_power(src, hierophant_cost) || !anchored)
to_chat(user, "You place your hand on [src], but it doesn't react.")
return
- var/choice = alert(user,"You place your hand on [src]...",,"Hierophant Broadcast","Spatial Gateway","Cancel")
+ var/choice = alert(user,"You place your hand on [src]...",,"Hierophant Broadcast","Spatial Gateway","Cancel") //Will create a stable gateway instead if between two obelisks one of which is onstation and the other on reebe
switch(choice)
if("Hierophant Broadcast")
if(active)
@@ -96,7 +101,7 @@
if(!anchored)
return
var/obj/effect/clockwork/spatial_gateway/SG = locate(/obj/effect/clockwork/spatial_gateway) in loc
- if(SG && SG.timerid) //it's a valid gateway, we're active
+ if(SG && (SG.timerid || SG.is_stable)) //it's a valid gateway, we're active
icon_state = active_icon
density = FALSE
active = TRUE
diff --git a/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm b/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm
index c01c7f0f57..5302153b9c 100644
--- a/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/eminence_spire.dm
@@ -11,7 +11,7 @@
var/selection_timer //Timer ID; this is canceled if the vote is canceled
var/kingmaking
-/obj/structure/destructible/clockwork/eminence_spire/attack_hand(mob/living/user)
+/obj/structure/destructible/clockwork/eminence_spire/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
@@ -27,9 +27,6 @@
if(C.clock_team.eminence)
to_chat(user, "There's already an Eminence!")
return
- if(!GLOB.servants_active)
- to_chat(user, "The Ark isn't active!")
- return
if(eminence_nominee) //This could be one large proc, but is split into three for ease of reading
if(eminence_nominee == user)
cancelation(user)
diff --git a/code/modules/antagonists/clockcult/clock_structures/heralds_beacon.dm b/code/modules/antagonists/clockcult/clock_structures/heralds_beacon.dm
index 7d8b206f41..f8a3afbf91 100644
--- a/code/modules/antagonists/clockcult/clock_structures/heralds_beacon.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/heralds_beacon.dm
@@ -1,3 +1,5 @@
+
+
//Used to "declare war" against the station. The servants' equipment will be permanently supercharged, and the Ark given extra time to prepare.
//This will send an announcement to the station, meaning that they will be warned very early in advance about the impending attack.
/obj/structure/destructible/clockwork/heralds_beacon
@@ -58,7 +60,7 @@
. += "There are [time_remaining] second[time_remaining != 1 ? "s" : ""] remaining to vote."
. += "There are [voters.len]/[votes_needed] votes to activate the beacon!"
-/obj/structure/destructible/clockwork/heralds_beacon/attack_hand(mob/living/user)
+/obj/structure/destructible/clockwork/heralds_beacon/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
@@ -108,5 +110,4 @@
to_chat(H, "The beacon's power warps your body into a clockwork form! You are now immune to many hazards, and your body is more robust against damage!")
H.set_species(/datum/species/golem/clockwork/no_scrap)
var/obj/structure/destructible/clockwork/massive/celestial_gateway/G = GLOB.ark_of_the_clockwork_justiciar
- G.grace_period = FALSE //no grace period if we've declared war
G.recalls_remaining++
diff --git a/code/modules/antagonists/clockcult/clock_structures/mania_motor.dm b/code/modules/antagonists/clockcult/clock_structures/mania_motor.dm
index 5fbaf9fd57..40cadb53a2 100644
--- a/code/modules/antagonists/clockcult/clock_structures/mania_motor.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/mania_motor.dm
@@ -30,7 +30,7 @@
toggle()
return TRUE
-/obj/structure/destructible/clockwork/powered/mania_motor/attack_hand(mob/living/user)
+/obj/structure/destructible/clockwork/powered/mania_motor/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
diff --git a/code/modules/antagonists/clockcult/clock_structures/prolonging_prism.dm b/code/modules/antagonists/clockcult/clock_structures/prolonging_prism.dm
new file mode 100644
index 0000000000..73488d736a
--- /dev/null
+++ b/code/modules/antagonists/clockcult/clock_structures/prolonging_prism.dm
@@ -0,0 +1,134 @@
+//Prolonging Prism: A prism that consumes power to delay the shuttle
+/obj/structure/destructible/clockwork/powered/prolonging_prism
+ name = "prolonging prism"
+ desc = "A dark onyx prism, held in midair by spiraling tendrils of stone."
+ clockwork_desc = "A powerful prism that will delay the arrival of an emergency shuttle."
+ icon_state = "prolonging_prism_inactive"
+ active_icon = "prolonging_prism"
+ inactive_icon = "prolonging_prism_inactive"
+ unanchored_icon = "prolonging_prism_unwrenched"
+ construction_value = 20
+ max_integrity = 125
+ break_message = "The prism falls to the ground with a heavy thud!"
+ debris = list(/obj/item/clockwork/alloy_shards/small = 3, \
+ /obj/item/clockwork/alloy_shards/medium = 1, \
+ /obj/item/clockwork/alloy_shards/large = 1, \
+ /obj/item/clockwork/component/vanguard_cogwheel/onyx_prism = 1)
+ var/static/power_refund = 250
+ var/static/delay_cost = 2000 //Updated power values for new-newclock. Easier to activate and sustain, you are quite literally pointing the entire station towards you as opposed to blood-delay after all.
+ var/static/delay_cost_increase = 1000
+ var/static/delay_remaining = 0
+
+/obj/structure/destructible/clockwork/powered/prolonging_prism/examine(mob/user)
+ . = ..()
+ if(is_servant_of_ratvar(user) || isobserver(user))
+ if(SSshuttle.emergency.mode == SHUTTLE_DOCKED || SSshuttle.emergency.mode == SHUTTLE_IGNITING || SSshuttle.emergency.mode == SHUTTLE_STRANDED || SSshuttle.emergency.mode == SHUTTLE_ESCAPE)
+ . += "An emergency shuttle has arrived and this prism is no longer useful; attempt to activate it to gain a partial refund of components used."
+ else
+ var/efficiency = get_efficiency_mod(TRUE)
+ . += "It requires at least [DisplayPower(get_delay_cost())] of power to attempt to delay the arrival of an emergency shuttle by [2 * efficiency] minutes."
+ . += "This cost increases by [DisplayPower(delay_cost_increase)] for every previous activation."
+
+/obj/structure/destructible/clockwork/powered/prolonging_prism/forced_disable(bad_effects)
+ if(active)
+ if(bad_effects)
+ try_use_power(MIN_CLOCKCULT_POWER*4)
+ visible_message("[src] emits an airy chuckling sound and falls dark!")
+ toggle()
+ return TRUE
+
+/obj/structure/destructible/clockwork/powered/prolonging_prism/on_attack_hand(mob/living/user)
+ if(user.canUseTopic(src, !issilicon(user), NO_DEXTERY) && is_servant_of_ratvar(user))
+ if(SSshuttle.emergency.mode == SHUTTLE_DOCKED || SSshuttle.emergency.mode == SHUTTLE_IGNITING || SSshuttle.emergency.mode == SHUTTLE_STRANDED || SSshuttle.emergency.mode == SHUTTLE_ESCAPE)
+ to_chat(user, "You break [src] apart, refunding some of the power used.")
+ adjust_clockwork_power(power_refund)
+ take_damage(max_integrity)
+ return 0
+ if(active)
+ return 0
+ var/turf/T = get_turf(src)
+ if(!T || !is_station_level(T.z))
+ to_chat(user, "[src] must be on the station to function!")
+ return 0
+ if(SSshuttle.emergency.mode != SHUTTLE_CALL)
+ to_chat(user, "No emergency shuttles are attempting to arrive at the station!")
+ return 0
+ if(!try_use_power(get_delay_cost()))
+ to_chat(user, "[src] needs more power to function!")
+ return 0
+ delay_cost += delay_cost_increase
+ delay_remaining += PRISM_DELAY_DURATION
+ toggle(0, user)
+
+/obj/structure/destructible/clockwork/powered/prolonging_prism/process()
+ var/turf/own_turf = get_turf(src)
+ if(SSshuttle.emergency.mode != SHUTTLE_CALL || delay_remaining <= 0 || !own_turf || !is_station_level(own_turf.z))
+ forced_disable(FALSE)
+ return
+ . = ..()
+ var/delay_amount = 40
+ delay_remaining -= delay_amount
+ var/efficiency = get_efficiency_mod()
+ SSshuttle.emergency.setTimer(SSshuttle.emergency.timeLeft(1) + (delay_amount * efficiency))
+ var/highest_y
+ var/highest_x
+ var/lowest_y
+ var/lowest_x
+ var/list/prism_turfs = list()
+ for(var/t in SSshuttle.emergency.ripple_area(SSshuttle.getDock("emergency_home")))
+ prism_turfs[t] = TRUE
+ var/turf/T = t
+ if(!highest_y || T.y > highest_y)
+ highest_y = T.y
+ if(!highest_x || T.x > highest_x)
+ highest_x = T.x
+ if(!lowest_y || T.y < lowest_y)
+ lowest_y = T.y
+ if(!lowest_x || T.x < lowest_x)
+ lowest_x = T.x
+ var/mean_y = LERP(lowest_y, highest_y, 0.5)
+ var/mean_x = LERP(lowest_x, highest_x, 0.5)
+ if(prob(50))
+ mean_y = CEILING(mean_y, 1)
+ else
+ mean_y = FLOOR(mean_y, 1) //Yes, I know round(mean_y) does the same, just left as FLOOR for consistancy sake
+ if(prob(50))
+ mean_x = CEILING(mean_x, 1)
+ else
+ mean_x = FLOOR(mean_x, 1)
+ var/turf/semi_random_center_turf = locate(mean_x, mean_y, z)
+ for(var/t in getline(src, semi_random_center_turf))
+ prism_turfs[t] = TRUE
+ var/placement_style = prob(50)
+ for(var/t in prism_turfs)
+ var/turf/T = t
+ if(placement_style)
+ if(ISODD(T.x + T.y))
+ seven_random_hexes(T, efficiency)
+ else if(prob(50 * efficiency))
+ new /obj/effect/temp_visual/ratvar/prolonging_prism(T)
+ else
+ if(ISEVEN(T.x + T.y))
+ seven_random_hexes(T, efficiency)
+ else if(prob(50 * efficiency))
+ new /obj/effect/temp_visual/ratvar/prolonging_prism(T)
+ CHECK_TICK //we may be going over a hell of a lot of turfs
+
+/obj/structure/destructible/clockwork/powered/prolonging_prism/proc/get_delay_cost()
+ return FLOOR(delay_cost, MIN_CLOCKCULT_POWER)
+
+/obj/structure/destructible/clockwork/powered/prolonging_prism/proc/seven_random_hexes(turf/T, efficiency)
+ var/static/list/hex_states = list("prismhex1", "prismhex2", "prismhex3", "prismhex4", "prismhex5", "prismhex6", "prismhex7")
+ var/mutable_appearance/hex_combo
+ for(var/n in hex_states) //BUILD ME A HEXAGON
+ if(prob(50 * efficiency))
+ if(!hex_combo)
+ hex_combo = mutable_appearance('icons/effects/64x64.dmi', n, RIPPLE_LAYER)
+ else
+ hex_combo.add_overlay(mutable_appearance('icons/effects/64x64.dmi', n, RIPPLE_LAYER))
+ if(hex_combo) //YOU BUILT A HEXAGON
+ hex_combo.pixel_x = -16
+ hex_combo.pixel_y = -16
+ hex_combo.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ hex_combo.plane = GAME_PLANE
+ new /obj/effect/temp_visual/ratvar/prolonging_prism(T, hex_combo)
diff --git a/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm
index c17885315f..73ae89a19b 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm
@@ -101,8 +101,8 @@
return
clashing = TRUE
GLOB.cult_narsie.clashing = TRUE
- to_chat(world, "\"YOU.\"")
- to_chat(world, "\"Ratvar?!\"")
+ to_chat(world, "\"[pick("YOU.", "BLOOD GOD!!", "FACE ME, COWARD!")]\"")
+ to_chat(world, "\"[pick("Ratvar?! How?!", "YOU. BANISHED ONCE. KILLED NOW.", "SCRAP HEAP!!")]\"")
clash_of_the_titans(GLOB.cult_narsie) // >:(
return TRUE
@@ -137,15 +137,16 @@
base_victory_chance *= 2 //The clash has a higher chance of resolving each time both gods attack one another
switch(winner)
if("Ratvar")
- send_to_playing_players("\"[pick("DIE.", "ROT.")]\"\n\
+ send_to_playing_players("\"[pick("DIE.", "ROT FOR CENTURIES, AS I HAVE!.","PERISH, HEATHEN.", "DIE, MONSTER, YOU DON'T BELONG IN THIS WORLD.")]\"\n\
\"[pick("Nooooo...", "Not die. To y-", "Die. Ratv-", "Sas tyen re-")]\"") //Nar'Sie get out
sound_to_playing_players('sound/magic/clockwork/anima_fragment_attack.ogg')
- sound_to_playing_players('sound/magic/demon_dies.ogg', 50)
+ sound_to_playing_players('sound/magic/abomscream.ogg', 50)
clashing = FALSE
qdel(narsie)
if("Nar'Sie")
- send_to_playing_players("\"[pick("Ha.", "Ra'sha fonn dest.", "You fool. To come here.")]\"") //Broken English
- sound_to_playing_players('sound/magic/demon_attack1.ogg')
- sound_to_playing_players('sound/magic/clockwork/anima_fragment_death.ogg', 62)
+ send_to_playing_players("\"[pick("Ha.", "Ra'sha fonn dest.", "You fool. To come here.")]\"\n\
+ \"[pick("NO, YOUR SHADOWS SHALL NO-", "ZNL GUR FGERNZF BS GVZR PNEEL ZL RKVFG-", "MY LIGHT CANNO-")]\"")
+ sound_to_playing_players('sound/magic/demon_attack1.ogg', 50)
+ sound_to_playing_players('sound/machines/clockcult/ratvar_scream.ogg', 80)
narsie.clashing = FALSE
qdel(src)
diff --git a/code/modules/antagonists/clockcult/clock_structures/trap_triggers/lever.dm b/code/modules/antagonists/clockcult/clock_structures/trap_triggers/lever.dm
index 12e4b62a65..55347685f4 100644
--- a/code/modules/antagonists/clockcult/clock_structures/trap_triggers/lever.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/trap_triggers/lever.dm
@@ -6,7 +6,7 @@
max_integrity = 75
icon_state = "lever"
-/obj/structure/destructible/clockwork/trap/trigger/lever/attack_hand(mob/living/user)
+/obj/structure/destructible/clockwork/trap/trigger/lever/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
diff --git a/code/modules/antagonists/clockcult/clock_structures/trap_triggers/repeater.dm b/code/modules/antagonists/clockcult/clock_structures/trap_triggers/repeater.dm
index f5ed91ac15..7a528786e2 100644
--- a/code/modules/antagonists/clockcult/clock_structures/trap_triggers/repeater.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/trap_triggers/repeater.dm
@@ -6,7 +6,7 @@
max_integrity = 15 //Fragile!
icon_state = "repeater"
-/obj/structure/destructible/clockwork/trap/trigger/repeater/attack_hand(mob/living/user)
+/obj/structure/destructible/clockwork/trap/trigger/repeater/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 5b2dd7d007..35151953a0 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -34,6 +34,8 @@
w_class = WEIGHT_CLASS_SMALL
force = 15
throwforce = 25
+ wound_bonus = -30
+ bare_wound_bonus = 30
armour_penetration = 35
actions_types = list(/datum/action/item_action/cult_dagger)
@@ -51,10 +53,12 @@
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
flags_1 = CONDUCT_1
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
w_class = WEIGHT_CLASS_BULKY
- force = 30
+ force = 30 // whoever balanced this got beat in the head by a bible too many times good lord
throwforce = 10
+ wound_bonus = -80
+ bare_wound_bonus = 30
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "rended")
@@ -110,7 +114,7 @@
armour_penetration = 45
throw_speed = 1
throw_range = 3
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
light_color = "#ff0000"
attack_verb = list("cleaved", "slashed", "torn", "hacked", "ripped", "diced", "carved")
icon_state = "cultbastard"
@@ -254,7 +258,7 @@
/datum/action/innate/cult/spin2win/Activate()
cooldown = world.time + sword.spin_cooldown
- holder.changeNext_move(50)
+ holder.DelayNextAction(50)
holder.apply_status_effect(/datum/status_effect/sword_spin)
sword.spinning = TRUE
sword.block_chance = 100
@@ -704,7 +708,7 @@
armour_penetration = 30
block_chance = 30
attack_verb = list("attacked", "impaled", "stabbed", "torn", "gored")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
hitsound = 'sound/weapons/bladeslice.ogg'
var/datum/action/innate/cult/spear/spear_act
var/wielded = FALSE // track wielded status on item
diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm
index 6f340b9271..5803941f36 100644
--- a/code/modules/antagonists/cult/cult_structures.dm
+++ b/code/modules/antagonists/cult/cult_structures.dm
@@ -44,15 +44,16 @@
/obj/structure/destructible/cult/attack_animal(mob/living/simple_animal/M)
if(istype(M, /mob/living/simple_animal/hostile/construct/builder))
if(obj_integrity < max_integrity)
- M.changeNext_move(CLICK_CD_MELEE)
+ M.DelayNextAction(CLICK_CD_MELEE)
obj_integrity = min(max_integrity, obj_integrity + 5)
Beam(M, icon_state="sendbeam", time=4)
M.visible_message("[M] repairs \the [src].", \
"You repair [src], leaving [p_they()] at [round(obj_integrity * 100 / max_integrity)]% stability.")
+ return TRUE
else
to_chat(M, "You cannot repair [src], as [p_theyre()] undamaged!")
else
- ..()
+ return ..()
/obj/structure/destructible/cult/attackby(obj/I, mob/user, params)
if(istype(I, /obj/item/melee/cultblade/dagger) && iscultist(user))
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index b0538d6521..3ea160c5cc 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -67,7 +67,7 @@ Runes can either be invoked by one's self or with many different cultists. Each
to_chat(user, "You disrupt the magic of [src] with [I].")
qdel(src)
-/obj/effect/rune/attack_hand(mob/living/user)
+/obj/effect/rune/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
diff --git a/code/modules/antagonists/devil/devil.dm b/code/modules/antagonists/devil/devil.dm
index c12259778e..3b6dc68986 100644
--- a/code/modules/antagonists/devil/devil.dm
+++ b/code/modules/antagonists/devil/devil.dm
@@ -92,6 +92,7 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
//Don't delete upon mind destruction, otherwise soul re-selling will break.
delete_on_mind_deletion = FALSE
threat = 5
+ show_to_ghosts = TRUE
var/obligation
var/ban
var/bane
diff --git a/code/modules/antagonists/devil/true_devil/_true_devil.dm b/code/modules/antagonists/devil/true_devil/_true_devil.dm
index 7d7031dad4..272b154828 100644
--- a/code/modules/antagonists/devil/true_devil/_true_devil.dm
+++ b/code/modules/antagonists/devil/true_devil/_true_devil.dm
@@ -144,7 +144,7 @@
/mob/living/carbon/true_devil/resist_fire()
//They're immune to fire.
-/mob/living/carbon/true_devil/attack_hand(mob/living/carbon/human/M)
+/mob/living/carbon/true_devil/on_attack_hand(mob/living/carbon/human/M)
. = ..()
if(.)
switch(M.a_intent)
diff --git a/code/modules/antagonists/disease/disease_datum.dm b/code/modules/antagonists/disease/disease_datum.dm
index 7de0330ad6..b8e906064c 100644
--- a/code/modules/antagonists/disease/disease_datum.dm
+++ b/code/modules/antagonists/disease/disease_datum.dm
@@ -2,6 +2,7 @@
name = "Sentient Disease"
roundend_category = "diseases"
antagpanel_category = "Disease"
+ show_to_ghosts = TRUE
var/disease_name = ""
/datum/antagonist/disease/on_gain()
diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm
index e876beb5dc..6a05d07b38 100644
--- a/code/modules/antagonists/disease/disease_mob.dm
+++ b/code/modules/antagonists/disease/disease_mob.dm
@@ -291,16 +291,19 @@ the new instance inside the host to be updated to the template's stats.
/mob/camera/disease/ClickOn(var/atom/A, params)
if(freemove && ishuman(A))
- var/mob/living/carbon/human/H = A
- if(alert(src, "Select [H.name] as your initial host?", "Select Host", "Yes", "No") != "Yes")
- return
- if(!freemove)
- return
- if(QDELETED(H) || !force_infect(H))
- to_chat(src, "[H ? H.name : "Host"] cannot be infected.")
+ confirm_initial_infection(A)
else
..()
+/mob/camera/disease/proc/confirm_initial_infection(mob/living/carbon/human/H)
+ set waitfor = FALSE
+ if(alert(src, "Select [H.name] as your initial host?", "Select Host", "Yes", "No") != "Yes")
+ return
+ if(!freemove)
+ return
+ if(QDELETED(H) || !force_infect(H))
+ to_chat(src, "[H ? H.name : "Host"] cannot be infected.")
+
/mob/camera/disease/proc/adapt_cooldown()
to_chat(src, "You have altered your genetic structure. You will be unable to adapt again for [DisplayTimeText(adaptation_cooldown)].")
next_adaptation_time = world.time + adaptation_cooldown
diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm
index 1d773627c7..295616d052 100644
--- a/code/modules/antagonists/ert/ert.dm
+++ b/code/modules/antagonists/ert/ert.dm
@@ -12,6 +12,7 @@
var/list/name_source
threat = -5
show_in_antagpanel = FALSE
+ show_to_ghosts = TRUE
antag_moodlet = /datum/mood_event/focused
/datum/antagonist/ert/on_gain()
diff --git a/code/modules/antagonists/monkey/monkey.dm b/code/modules/antagonists/monkey/monkey.dm
index ebb39c814e..971532958f 100644
--- a/code/modules/antagonists/monkey/monkey.dm
+++ b/code/modules/antagonists/monkey/monkey.dm
@@ -9,6 +9,7 @@
roundend_category = "monkeys"
antagpanel_category = "Monkey"
threat = 3
+ show_to_ghosts = TRUE
var/datum/team/monkey/monkey_team
var/monkey_only = TRUE
diff --git a/code/modules/antagonists/nightmare/nightmare.dm b/code/modules/antagonists/nightmare/nightmare.dm
index 837b6e4216..f5b10de5c2 100644
--- a/code/modules/antagonists/nightmare/nightmare.dm
+++ b/code/modules/antagonists/nightmare/nightmare.dm
@@ -3,3 +3,4 @@
show_in_antagpanel = FALSE
show_name_in_check_antagonists = TRUE
threat = 5
+ show_to_ghosts = TRUE
diff --git a/code/modules/antagonists/ninja/ninja.dm b/code/modules/antagonists/ninja/ninja.dm
index 2615822dd8..414f7dd6b0 100644
--- a/code/modules/antagonists/ninja/ninja.dm
+++ b/code/modules/antagonists/ninja/ninja.dm
@@ -3,6 +3,7 @@
antagpanel_category = "Ninja"
job_rank = ROLE_NINJA
show_name_in_check_antagonists = TRUE
+ show_to_ghosts = TRUE
antag_moodlet = /datum/mood_event/focused
threat = 8
var/helping_station = FALSE
diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
index a11ecaa3df..69147d76e8 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -8,13 +8,12 @@
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
var/timer_set = 90
- var/default_timer_set = 90
var/minimum_timer_set = 90
var/maximum_timer_set = 3600
- ui_style = "nanotrasen"
var/numeric_input = ""
var/ui_mode = NUKEUI_AWAIT_DISK
+
var/timing = FALSE
var/exploding = FALSE
var/exploded = FALSE
@@ -31,7 +30,6 @@
var/interior = ""
var/proper_bomb = TRUE //Please
var/obj/effect/countdown/nuclearbomb/countdown
- var/nuclear_cooldown //used to stop global spam.
/obj/machinery/nuclearbomb/Initialize()
. = ..()
@@ -74,15 +72,16 @@
/obj/machinery/nuclearbomb/syndicate/get_cinematic_type(off_station)
var/datum/game_mode/nuclear/NM = SSticker.mode
switch(off_station)
- if(FALSE)
+ if(0)
if(istype(NM) && !NM.nuke_team.syndies_escaped())
return CINEMATIC_ANNIHILATION
else
return CINEMATIC_NUKE_WIN
- if(NUKE_MISS_STATION)
+ if(1)
return CINEMATIC_NUKE_MISS
- else
+ if(2)
return CINEMATIC_NUKE_FAR
+ return CINEMATIC_NUKE_FAR
/obj/machinery/nuclearbomb/proc/disk_check(obj/item/disk/nuclear/D)
if(D.fake)
@@ -191,7 +190,7 @@
icon_state = "nuclearbomb_exploding"
/obj/machinery/nuclearbomb/update_overlays()
- . = ..()
+ . += ..()
update_icon_interior()
update_icon_lights()
@@ -233,7 +232,7 @@
explode()
else
var/volume = (get_time_left() <= 20 ? 30 : 5)
- playsound(loc, 'sound/items/timer.ogg', volume, 0)
+ playsound(loc, 'sound/items/timer.ogg', volume, FALSE)
/obj/machinery/nuclearbomb/proc/update_ui_mode()
if(exploded)
@@ -258,18 +257,18 @@
ui_mode = NUKEUI_AWAIT_TIMER
-
-/obj/machinery/nuclearbomb/ui_interact(mob/user, ui_key="main", datum/tgui/ui=null, force_open=0, datum/tgui/master_ui=null, datum/ui_state/state=GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/nuclearbomb/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "nuclear_bomb", name, 350, 442, master_ui, state)
- ui.set_style(ui_style)
+ ui = new(user, src, "NuclearBomb", name)
ui.open()
/obj/machinery/nuclearbomb/ui_data(mob/user)
var/list/data = list()
data["disk_present"] = auth
+
var/hidden_code = (ui_mode == NUKEUI_AWAIT_CODE && numeric_input != "ERROR")
+
var/current_code = ""
if(hidden_code)
while(length(current_code) < length(numeric_input))
@@ -386,14 +385,13 @@
if("anchor")
if(auth && yes_code)
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
- set_anchor(usr)
+ set_anchor()
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
-
-/obj/machinery/nuclearbomb/proc/set_anchor(mob/user)
- if((istype(get_area(src), /area/space) || isinspace()) && !anchored)
- to_chat(user, "This is not a suitable platform for anchoring [src]!")
+/obj/machinery/nuclearbomb/proc/set_anchor()
+ if(isinspace() && !anchored)
+ to_chat(usr, "There is nothing to anchor to!")
else
anchored = !anchored
@@ -414,9 +412,6 @@
if(safety)
to_chat(usr, "The safety is still on.")
return
- if(!timing && nuclear_cooldown > world.time)
- to_chat(usr, "[src]'s timer protocols are currently on cooldown, please stand by.")
- return
timing = !timing
if(timing)
previous_level = NUM2SECLEVEL(GLOB.security_level)
@@ -425,12 +420,6 @@
S.switch_mode_to(TRACK_INFILTRATOR)
countdown.start()
set_security_level("delta")
- nuclear_cooldown = world.time + 15 SECONDS
-
- if(GLOB.war_declared)
- var/area/A = get_area(src)
- priority_announce("Alert: Unexpected increase in radiation levels near [A.name] ([src.x],[src.y],[src.z]). Please send an authorized radiation specialist to investigate.", "Sensory Nuclear Indexer Telemetry Calculation Helper")
-
else
detonation_timer = null
set_security_level(previous_level)
@@ -481,12 +470,19 @@
var/off_station = FALSE
var/turf/bomb_location = get_turf(src)
- if(!bomb_location || !is_station_level(bomb_location.z))
- off_station = NUKE_MISS_STATION
+ var/area/A = get_area(bomb_location)
+
+ if(bomb_location && is_station_level(bomb_location.z))
+ if(istype(A, /area/space))
+ off_station = NUKE_NEAR_MISS
+ if((bomb_location.x < (128-NUKERANGE)) || (bomb_location.x > (128+NUKERANGE)) || (bomb_location.y < (128-NUKERANGE)) || (bomb_location.y > (128+NUKERANGE)))
+ off_station = NUKE_NEAR_MISS
else if(bomb_location.onSyndieBase())
off_station = NUKE_SYNDICATE_BASE
+ else
+ off_station = NUKE_MISS_STATION
- if(!off_station)
+ if(off_station < 2) //can only launch when nuke is on syndie base or space
SSshuttle.registerHostileEnvironment(src)
SSshuttle.lockdown = TRUE
@@ -500,13 +496,13 @@
INVOKE_ASYNC(GLOBAL_PROC,.proc/KillEveryoneOnZLevel, z)
/obj/machinery/nuclearbomb/proc/get_cinematic_type(off_station)
- if(!off_station)
+ if(off_station < 2)
return CINEMATIC_SELFDESTRUCT
else
return CINEMATIC_SELFDESTRUCT_MISS
/obj/machinery/nuclearbomb/beer
- name = "Nanotrasen-brand nuclear fission explosive"
+ name = "\improper Nanotrasen-brand nuclear fission explosive"
desc = "One of the more successful achievements of the Nanotrasen Corporate Warfare Division, their nuclear fission explosives are renowned for being cheap to produce and devastatingly effective. Signs explain that though this particular device has been decommissioned, every Nanotrasen station is equipped with an equivalent one, just in case. All Captains carefully guard the disk needed to detonate them - at least, the sign says they do. There seems to be a tap on the back."
proper_bomb = FALSE
var/obj/structure/reagent_dispensers/beerkeg/keg
@@ -519,9 +515,9 @@
/obj/machinery/nuclearbomb/beer/examine(mob/user)
. = ..()
if(keg.reagents.total_volume)
- . += "It has [keg.reagents.total_volume] unit\s left."
+ to_chat(user, "It has [keg.reagents.total_volume] unit\s left.")
else
- . += "It's empty."
+ to_chat(user, "It's empty.")
/obj/machinery/nuclearbomb/beer/attackby(obj/item/W, mob/user, params)
if(W.is_refillable())
@@ -533,6 +529,8 @@
return ..()
/obj/machinery/nuclearbomb/beer/actually_explode()
+ //Unblock roundend, we're not actually exploding.
+ SSticker.roundend_check_paused = FALSE
var/turf/bomb_location = get_turf(src)
if(!bomb_location)
disarm()
@@ -581,7 +579,7 @@
This is here to make the tiles around the station mininuke change when it's armed.
*/
-/obj/machinery/nuclearbomb/selfdestruct/set_anchor(mob/user)
+/obj/machinery/nuclearbomb/selfdestruct/set_anchor()
return
/obj/machinery/nuclearbomb/selfdestruct/set_active()
@@ -639,18 +637,19 @@ This is here to make the tiles around the station mininuke change when it's arme
if(newturf && lastlocation == newturf)
if(last_disk_move < world.time - 5000 && prob((world.time - 5000 - last_disk_move)*0.0001))
var/datum/round_event_control/operative/loneop = locate(/datum/round_event_control/operative) in SSevents.control
- if(istype(loneop))
+ if(istype(loneop) && loneop.occurrences < loneop.max_occurrences)
loneop.weight += 1
- if(loneop.weight % 5 == 0)
+ if(loneop.weight % 5 == 0 && SSticker.totalPlayers > 1 && (CONFIG_GET(flag/admin_disk_inactive_msg))) //players count now
message_admins("[src] is stationary in [ADMIN_VERBOSEJMP(newturf)]. The weight of Lone Operative is now [loneop.weight].")
log_game("[src] is stationary for too long in [loc_name(newturf)], and has increased the weight of the Lone Operative event to [loneop.weight].")
+
else
lastlocation = newturf
last_disk_move = world.time
var/datum/round_event_control/operative/loneop = locate(/datum/round_event_control/operative) in SSevents.control
- if(istype(loneop) && prob(loneop.weight))
+ if(istype(loneop) && loneop.occurrences < loneop.max_occurrences && prob(loneop.weight))
loneop.weight = max(loneop.weight - 1, 0)
- if(loneop.weight % 5 == 0)
+ if(loneop.weight % 5 == 0 && SSticker.totalPlayers > 1)
message_admins("[src] is on the move (currently in [ADMIN_VERBOSEJMP(newturf)]). The weight of Lone Operative is now [loneop.weight].")
log_game("[src] being on the move has reduced the weight of the Lone Operative event to [loneop.weight].")
@@ -659,9 +658,19 @@ This is here to make the tiles around the station mininuke change when it's arme
if(!fake)
return
- if(isobserver(user) || HAS_TRAIT(user, TRAIT_DISK_VERIFIER) || (user.mind && HAS_TRAIT(user.mind, TRAIT_DISK_VERIFIER)))
+ if(isobserver(user) || HAS_TRAIT(user.mind, TRAIT_DISK_VERIFIER))
. += "The serial numbers on [src] are incorrect."
+/*
+ * You can't accidentally eat the nuke disk, bro
+ */
+ /*
+/obj/item/disk/nuclear/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ M.visible_message("[M] looks like [M.p_theyve()] just bitten into something important.", \
+ "Wait, is this the nuke disk?")
+
+ return discover_after
+*/
/obj/item/disk/nuclear/attackby(obj/item/I, mob/living/user, params)
if(istype(I, /obj/item/claymore/highlander) && !fake)
var/obj/item/claymore/highlander/H = I
@@ -684,7 +693,7 @@ This is here to make the tiles around the station mininuke change when it's arme
/obj/item/disk/nuclear/suicide_act(mob/user)
user.visible_message("[user] is going delta! It looks like [user.p_theyre()] trying to commit suicide!")
- playsound(src, 'sound/machines/alarm.ogg', 50, -1, 1)
+ playsound(src, 'sound/machines/alarm.ogg', 50, -1, TRUE)
for(var/i in 1 to 100)
addtimer(CALLBACK(user, /atom/proc/add_atom_colour, (i % 2)? "#00FF00" : "#FF0000", ADMIN_COLOUR_PRIORITY), i)
addtimer(CALLBACK(src, .proc/manual_suicide, user), 101)
@@ -692,7 +701,7 @@ This is here to make the tiles around the station mininuke change when it's arme
/obj/item/disk/nuclear/proc/manual_suicide(mob/living/user)
user.remove_atom_colour(ADMIN_COLOUR_PRIORITY)
- user.visible_message("[user] was destroyed by the nuclear blast!")
+ user.visible_message("[user] is destroyed by the nuclear blast!")
user.adjustOxyLoss(200)
user.death(0)
diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm
index 454cde6d72..652b19a8e7 100644
--- a/code/modules/antagonists/nukeop/nukeop.dm
+++ b/code/modules/antagonists/nukeop/nukeop.dm
@@ -6,6 +6,7 @@
antag_moodlet = /datum/mood_event/focused
threat = 10
skill_modifiers = list(/datum/skill_modifier/job/level/wiring)
+ show_to_ghosts = TRUE
var/datum/team/nuclear/nuke_team
var/always_new_team = FALSE //If not assigned a team by default ops will try to join existing ones, set this to TRUE to always create new team.
var/send_to_spawnpoint = TRUE //Should the user be moved to default spawnpoint.
diff --git a/code/modules/antagonists/official/official.dm b/code/modules/antagonists/official/official.dm
index 1d340253c4..1ec64cb2b6 100644
--- a/code/modules/antagonists/official/official.dm
+++ b/code/modules/antagonists/official/official.dm
@@ -4,6 +4,7 @@
show_in_antagpanel = FALSE
var/datum/objective/mission
var/datum/team/ert/ert_team
+ show_to_ghosts = TRUE
/datum/antagonist/official/greet()
to_chat(owner, "You are a CentCom Official.")
diff --git a/code/modules/antagonists/pirate/pirate.dm b/code/modules/antagonists/pirate/pirate.dm
index 01f3c6068e..e6d350064d 100644
--- a/code/modules/antagonists/pirate/pirate.dm
+++ b/code/modules/antagonists/pirate/pirate.dm
@@ -4,6 +4,7 @@
roundend_category = "space pirates"
antagpanel_category = "Pirate"
threat = 5
+ show_to_ghosts = TRUE
var/datum/team/pirate/crew
/datum/antagonist/pirate/greet()
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index d0ef5a83ce..21ccd425a3 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -112,7 +112,7 @@
if(stasis)
return
if(revealed && essence <= 0)
- death()
+ INVOKE_ASYNC(src, .proc/death)
if(unreveal_time && world.time >= unreveal_time)
unreveal_time = 0
revealed = FALSE
diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/antagonists/revenant/revenant_abilities.dm
index 2d84ed7c22..7a2f661fd9 100644
--- a/code/modules/antagonists/revenant/revenant_abilities.dm
+++ b/code/modules/antagonists/revenant/revenant_abilities.dm
@@ -17,6 +17,7 @@
//Harvest; activated ly clicking the target, will try to drain their essence.
/mob/living/simple_animal/revenant/proc/Harvest(mob/living/carbon/human/target)
+ set waitfor = FALSE
if(!castcheck(0))
return
if(draining)
diff --git a/code/modules/antagonists/revenant/revenant_antag.dm b/code/modules/antagonists/revenant/revenant_antag.dm
index 46c1176533..c93291797a 100644
--- a/code/modules/antagonists/revenant/revenant_antag.dm
+++ b/code/modules/antagonists/revenant/revenant_antag.dm
@@ -3,6 +3,7 @@
show_in_antagpanel = FALSE
show_name_in_check_antagonists = TRUE
threat = 5
+ show_to_ghosts = TRUE
/datum/antagonist/revenant/greet()
owner.announce_objectives()
diff --git a/code/modules/antagonists/santa/santa.dm b/code/modules/antagonists/santa/santa.dm
index f58a21ba42..ff7dae98f6 100644
--- a/code/modules/antagonists/santa/santa.dm
+++ b/code/modules/antagonists/santa/santa.dm
@@ -1,6 +1,8 @@
/datum/antagonist/santa
name = "Santa"
show_in_antagpanel = FALSE
+ show_name_in_check_antagonists = TRUE
+ show_to_ghosts = TRUE
/datum/antagonist/santa/on_gain()
. = ..()
diff --git a/code/modules/antagonists/slaughter/slaughter.dm b/code/modules/antagonists/slaughter/slaughter.dm
index 6b063559dc..3ae9cfcfb0 100644
--- a/code/modules/antagonists/slaughter/slaughter.dm
+++ b/code/modules/antagonists/slaughter/slaughter.dm
@@ -34,8 +34,11 @@
healable = 0
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
obj_damage = 50
- melee_damage_lower = 30
- melee_damage_upper = 30
+ melee_damage_lower = 22.5 // reduced from 30 to 22.5 with wounds since they get big buffs to slicing wounds
+ melee_damage_upper = 22.5
+ wound_bonus = -10
+ bare_wound_bonus = 0
+ sharpness = SHARP_EDGED
see_in_dark = 8
blood_volume = 0 //No bleeding on getting shot, for skeddadles
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
@@ -43,13 +46,25 @@
var/playstyle_string = "You are a slaughter demon, a terrible creature from another realm. You have a single desire: To kill. \
You may use the \"Blood Crawl\" ability near blood pools to travel through them, appearing and disappearing from the station at will. \
Pulling a dead or unconscious mob while you enter a pool will pull them in with you, allowing you to feast and regain your health. \
- You move quickly upon leaving a pool of blood, but the material world will soon sap your strength and leave you sluggish. "
+ You move quickly upon leaving a pool of blood, but the material world will soon sap your strength and leave you sluggish. \
+ You gain strength the more attacks you land on live humanoids, though this resets when you return to the blood zone. You can also \
+ launch a devastating slam attack with ctrl+shift+click, capable of smashing bones in one strike."
loot = list(/obj/effect/decal/cleanable/blood, \
/obj/effect/decal/cleanable/blood/innards, \
/obj/item/organ/heart/demon)
del_on_death = 1
deathmessage = "screams in anger as it collapses into a puddle of viscera!"
+ // How long it takes for the alt-click slam attack to come off cooldown
+ var/slam_cooldown_time = 45 SECONDS
+ // The actual instance var for the cooldown
+ var/slam_cooldown = 0
+ // How many times we have hit humanoid targets since we last bloodcrawled, scaling wounding power
+ var/current_hitstreak = 0
+ // How much both our wound_bonus and bare_wound_bonus go up per hitstreak hit
+ var/wound_bonus_per_hit = 5
+ // How much our wound_bonus hitstreak bonus caps at (peak demonry)
+ var/wound_bonus_hitstreak_max = 12
/mob/living/simple_animal/slaughter/Initialize()
..()
@@ -58,6 +73,33 @@
if(istype(loc, /obj/effect/dummy/phased_mob/slaughter))
bloodspell.phased = TRUE
+/mob/living/simple_animal/slaughter/CtrlShiftClickOn(atom/A)
+ if(!isliving(A))
+ return ..()
+ if(slam_cooldown + slam_cooldown_time > world.time)
+ to_chat(src, "Your slam ability is still on cooldown!")
+ return
+
+ face_atom(A)
+ var/mob/living/victim = A
+ victim.take_bodypart_damage(brute=20, wound_bonus=wound_bonus) // don't worry, there's more punishment when they hit something
+ visible_message("[src] slams into [victim] with monstrous strength!", "You slam into [victim] with monstrous strength!", ignored_mobs=victim)
+ to_chat(victim, "[src] slams into you with monstrous strength, sending you flying like a ragdoll!")
+ var/turf/yeet_target = get_edge_target_turf(victim, dir)
+ victim.throw_at(yeet_target, 10, 5, src)
+ slam_cooldown = world.time
+ log_combat(src, victim, "slaughter slammed")
+
+/mob/living/simple_animal/slaughter/UnarmedAttack(atom/A, proximity)
+ if(iscarbon(A))
+ var/mob/living/carbon/target = A
+ if(target.stat != DEAD && target.mind && current_hitstreak < wound_bonus_hitstreak_max)
+ current_hitstreak++
+ wound_bonus += wound_bonus_per_hit
+ bare_wound_bonus += wound_bonus_per_hit
+
+ return ..()
+
/obj/effect/decal/cleanable/blood/innards
icon = 'icons/obj/surgery.dmi'
name = "pile of viscera"
diff --git a/code/modules/antagonists/slaughter/slaughter_antag.dm b/code/modules/antagonists/slaughter/slaughter_antag.dm
index 04f7167fa5..d6d504ef8e 100644
--- a/code/modules/antagonists/slaughter/slaughter_antag.dm
+++ b/code/modules/antagonists/slaughter/slaughter_antag.dm
@@ -6,6 +6,7 @@
threat = 10
job_rank = ROLE_ALIEN
show_in_antagpanel = FALSE
+ show_to_ghosts = TRUE
/datum/antagonist/slaughter/on_gain()
forge_objectives()
@@ -14,6 +15,7 @@
/datum/antagonist/slaughter/greet()
. = ..()
owner.announce_objectives()
+ to_chat(owner, "You have a powerful alt-attack that slams people backwards that you can activate by shift+ctrl+clicking your target!")
/datum/antagonist/slaughter/proc/forge_objectives()
if(summoner)
diff --git a/code/modules/antagonists/survivalist/survivalist.dm b/code/modules/antagonists/survivalist/survivalist.dm
index 04ad53f65b..296369fe3b 100644
--- a/code/modules/antagonists/survivalist/survivalist.dm
+++ b/code/modules/antagonists/survivalist/survivalist.dm
@@ -23,6 +23,18 @@
/datum/antagonist/survivalist/guns
greet_message = "Your own safety matters above all else, and the only way to ensure your safety is to stockpile weapons! Grab as many guns as possible, and don't let anyone take them!"
+/datum/antagonist/survivalist/guns/forge_objectives()
+ var/datum/objective/steal_five_of_type/summon_guns/guns = new
+ guns.owner = owner
+ objectives += guns
+ ..()
+
/datum/antagonist/survivalist/magic
name = "Amateur Magician"
greet_message = "This magic stuff is... so powerful. You want more. More! They want your power. They can't have it! Don't let them have it!"
+
+/datum/antagonist/survivalist/magic/forge_objectives()
+ var/datum/objective/steal_five_of_type/summon_magic/magic = new
+ magic.owner = owner
+ objectives += magic
+ ..()
diff --git a/code/modules/antagonists/swarmer/swarmer.dm b/code/modules/antagonists/swarmer/swarmer.dm
index d0e36394ab..b7503f4b22 100644
--- a/code/modules/antagonists/swarmer/swarmer.dm
+++ b/code/modules/antagonists/swarmer/swarmer.dm
@@ -36,7 +36,7 @@
if(A)
notify_ghosts("A swarmer shell has been created in [A.name].", 'sound/effects/bin_close.ogg', source = src, action = NOTIFY_ATTACK, flashwindow = FALSE, ignore_dnr_observers = TRUE)
-/obj/effect/mob_spawn/swarmer/attack_hand(mob/living/user)
+/obj/effect/mob_spawn/swarmer/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
@@ -158,10 +158,11 @@
face_atom(A)
if(!isturf(loc))
return
- if(next_move > world.time)
+ if(!CheckActionCooldown())
return
if(!A.Adjacent(src))
return
+ DelayNextAction()
A.swarmer_act(src)
/atom/proc/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
@@ -399,13 +400,13 @@
return FALSE
/obj/structure/lattice/catwalk/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- . = ..()
var/turf/here = get_turf(src)
for(var/A in here.contents)
var/obj/structure/cable/C = A
if(istype(C))
to_chat(S, "Disrupting the power grid would bring no benefit to us. Aborting.")
return FALSE
+ return ..()
/obj/item/deactivated_swarmer/IntegrateAmount()
return 50
@@ -486,7 +487,7 @@
var/obj/O = target
if(O.resistance_flags & INDESTRUCTIBLE)
return FALSE
- for(var/mob/living/L in GetAllContents())
+ for(var/mob/living/L in target.GetAllContents())
if(!ispAI(L) && !isbrain(L))
to_chat(src, "An organism has been detected inside this object. Aborting.")
return FALSE
@@ -497,7 +498,7 @@
if(resource_gain)
resources += resource_gain
do_attack_animation(target)
- changeNext_move(CLICK_CD_MELEE)
+ DelayNextAction(CLICK_CD_MELEE)
var/obj/effect/temp_visual/swarmer/integrate/I = new /obj/effect/temp_visual/swarmer/integrate(get_turf(target))
I.pixel_x = target.pixel_x
I.pixel_y = target.pixel_y
@@ -517,10 +518,9 @@
/mob/living/simple_animal/hostile/swarmer/proc/DisIntegrate(atom/movable/target)
new /obj/effect/temp_visual/swarmer/disintegration(get_turf(target))
do_attack_animation(target)
- changeNext_move(CLICK_CD_MELEE)
+ DelayNextAction(CLICK_CD_MELEE)
target.ex_act(EXPLODE_LIGHT)
-
/mob/living/simple_animal/hostile/swarmer/proc/DisperseTarget(mob/living/target)
if(target == src)
return
diff --git a/code/modules/antagonists/traitor/IAA/internal_affairs.dm b/code/modules/antagonists/traitor/IAA/internal_affairs.dm
index 19144d67c9..ff012e556a 100644
--- a/code/modules/antagonists/traitor/IAA/internal_affairs.dm
+++ b/code/modules/antagonists/traitor/IAA/internal_affairs.dm
@@ -167,10 +167,10 @@
return
if(last_man_standing)
if(syndicate)
- to_chat(owner.current," All the loyalist agents are dead, and no more is required of you. Die a glorious death, agent. ")
+ to_chat(owner.current,"All the suspected agents are dead, and no more is required of you. Die a glorious death, agent.")
+ replace_escape_objective(owner)
else
- to_chat(owner.current," All the other agents are dead, and you're the last loose end. Stage a Syndicate terrorist attack to cover up for today's events. You no longer have any limits on collateral damage.")
- replace_escape_objective(owner)
+ to_chat(owner.current,"All the other agents are dead. You have done us all a great service and shall be honorably exiled upon returning to base.")
/datum/antagonist/traitor/internal_affairs/proc/iaa_process()
if(owner&&owner.current&&owner.current.stat!=DEAD)
@@ -193,7 +193,7 @@
if(syndicate)
fail_msg += " You no longer have permission to die. "
else
- fail_msg += " The truth could still slip out! Cease any terrorist actions as soon as possible, unneeded property damage or loss of employee life will lead to your contract being terminated."
+ fail_msg += " The truth could still slip out! Cease any terrorist actions as soon as possible, unneeded property damage or loss of employee life will lead to great shame."
reinstate_escape_objective(owner)
last_man_standing = FALSE
to_chat(owner.current, fail_msg)
@@ -226,18 +226,20 @@
add_objective(escape_objective)
/datum/antagonist/traitor/internal_affairs/proc/greet_iaa()
- var/crime = pick("distribution of contraband" , "unauthorized erotic action on duty", "embezzlement", "piloting under the influence", "dereliction of duty", "syndicate collaboration", "mutiny", "multiple homicides", "corporate espionage", "receiving bribes", "malpractice", "worship of prohibited life forms", "possession of profane texts", "murder", "arson", "insulting their manager", "grand theft", "conspiracy", "attempting to unionize", "vandalism", "gross incompetence")
+ var/crime = pick("distribution of contraband" , "embezzlement", "piloting under the influence", "dereliction of duty", "syndicate collaboration", "mutiny", "multiple homicides", "corporate espionage", "receiving bribes", "malpractice", "worship of prohibited life forms", "possession of profane texts", "murder", "arson", "insulting their manager", "grand theft", "conspiracy", "attempting to unionize", "vandalism", "gross incompetence")
to_chat(owner.current, "You are the [special_role].")
if(syndicate)
- to_chat(owner.current, "Your target has been framed for [crime], and you have been tasked with eliminating them to prevent them defending themselves in court.")
- to_chat(owner.current, "Any damage you cause will be a further embarrassment to Nanotrasen, so you have no limits on collateral damage.")
- to_chat(owner.current, " You have been provided with a standard uplink to accomplish your task. ")
- to_chat(owner.current, "By no means reveal that you, or any other NT employees, are undercover agents.")
+ to_chat(owner.current, "GREAT LEADER IS DEAD. NANOTRASEN MUST FALL.")
+ to_chat(owner.current, "Your have infiltrated this vessel to cause chaos and assassinate targets known to have conspired against the Syndicate.")
+ to_chat(owner.current, "Any damage you cause will be a further embarrassment to Nanotrasen, so you have no limits on collateral damage.")
+ to_chat(owner.current, "You have been provided with a standard uplink to accomplish your task. ")
+ to_chat(owner.current, "By no means reveal that you are a Syndicate agent. By no means reveal that your targets are being hunted.")
else
- to_chat(owner.current, "Your target is suspected of [crime], and you have been tasked with eliminating them by any means necessary to avoid a costly and embarrassing public trial.")
- to_chat(owner.current, "While you have a license to kill, unneeded property damage or loss of employee life will lead to your contract being terminated.")
- to_chat(owner.current, "For the sake of plausible deniability, you have been equipped with an array of captured Syndicate weaponry available via uplink.")
+ to_chat(owner.current, "CAUTION: Your legal status as a citizen of NanoTrasen will be permanently revoked upon completion of your first contract.")
+ to_chat(owner.current, "Your target has been suspected of [crime], and must be removed from this plane.")
+ to_chat(owner.current, "While you have a license to kill, you are to eliminate your targets with no collateral or unrelated deaths.")
+ to_chat(owner.current, "For the sake of plausable deniability, you have been equipped with captured Syndicate equipment via uplink.")
to_chat(owner.current, "By no means reveal that you, or any other NT employees, are undercover agents.")
to_chat(owner.current, "Finally, watch your back. Your target has friends in high places, and intel suggests someone may have taken out a contract of their own to protect them.")
diff --git a/code/modules/antagonists/traitor/equipment/contractor.dm b/code/modules/antagonists/traitor/equipment/contractor.dm
index 6c5d5766e4..94a3059b5f 100644
--- a/code/modules/antagonists/traitor/equipment/contractor.dm
+++ b/code/modules/antagonists/traitor/equipment/contractor.dm
@@ -1,4 +1,4 @@
-// Support unit gets it's own very basic antag datum for admin logging.
+/// Support unit gets it's own very basic antag datum for admin logging.
/datum/antagonist/traitor/contractor_support
name = "Contractor Support Unit"
antag_moodlet = /datum/mood_event/focused
@@ -8,11 +8,13 @@
should_equip = FALSE /// Don't give them an uplink.
var/datum/team/contractor_team/contractor_team
-/datum/team/contractor_team // Team for storing both the contractor and their support unit - only really for the HUD and admin logging.
+/// Team for storing both the contractor and their support unit - only really for the HUD and admin logging.
+/datum/team/contractor_team
show_roundend_report = FALSE
/datum/antagonist/traitor/contractor_support/forge_traitor_objectives()
var/datum/objective/generic_objective = new
+
generic_objective.name = "Follow Contractor's Orders"
generic_objective.explanation_text = "Follow your orders. Assist agents in this mission area."
generic_objective.completed = TRUE
@@ -25,7 +27,9 @@
var/static/list/contractor_items = typecacheof(/datum/contractor_item/, TRUE)
var/datum/syndicate_contract/current_contract
var/list/datum/syndicate_contract/assigned_contracts = list()
+
var/list/assigned_targets = list() // used as a blacklist to make sure we're not assigning targets already assigned
+ var/contracts_completed = 0
var/contract_TC_payed_out = 0 // Keeping track for roundend reporting
var/contract_TC_to_redeem = 0 // Used internally and roundend reporting - what TC we have available to cashout.
@@ -34,7 +38,8 @@
var/datum/contractor_item/contractor_item = new path
hub_items.Add(contractor_item)
-/datum/contractor_hub/proc/create_contracts(datum/mind/owner) // 6 initial contracts
+/datum/contractor_hub/proc/create_contracts(datum/mind/owner)
+ // 6 initial contracts
var/list/to_generate = list(
CONTRACT_PAYOUT_LARGE,
CONTRACT_PAYOUT_MEDIUM,
@@ -44,61 +49,74 @@
CONTRACT_PAYOUT_SMALL
)
- var/lowest_TC_threshold = 30 // We don't want the sum of all the payouts to be under this amount
+ //What the fuck
+ if(length(to_generate) > length(GLOB.data_core.locked))
+ to_generate.Cut(1, length(GLOB.data_core.locked))
+ // We don't want the sum of all the payouts to be under this amount
+ var/lowest_TC_threshold = 30
+
var/total = 0
var/lowest_paying_sum = 0
var/datum/syndicate_contract/lowest_paying_contract
- to_generate = shuffle(to_generate) // Randomise order, so we don't have contracts always in payout order.
- var/start_index = 1 // Support contract generation happening multiple times
- if(assigned_contracts.len != 0)
+ // Randomise order, so we don't have contracts always in payout order.
+ to_generate = shuffle(to_generate)
+ // Support contract generation happening multiple times
+ var/start_index = 1
+ if (assigned_contracts.len != 0)
start_index = assigned_contracts.len + 1
- for(var/i = 1; i <= to_generate.len; i++) // Generate contracts, and find the lowest paying.
+ // Generate contracts, and find the lowest paying.
+ for (var/i = 1; i <= to_generate.len; i++)
var/datum/syndicate_contract/contract_to_add = new(owner, assigned_targets, to_generate[i])
var/contract_payout_total = contract_to_add.contract.payout + contract_to_add.contract.payout_bonus
assigned_targets.Add(contract_to_add.contract.target)
- if(!lowest_paying_contract || (contract_payout_total < lowest_paying_sum))
+ if (!lowest_paying_contract || (contract_payout_total < lowest_paying_sum))
lowest_paying_sum = contract_payout_total
lowest_paying_contract = contract_to_add
total += contract_payout_total
contract_to_add.id = start_index
assigned_contracts.Add(contract_to_add)
start_index++
- if(total < lowest_TC_threshold) // If the threshold for TC payouts isn't reached, boost the lowest paying contract
+
+ // If the threshold for TC payouts isn't reached, boost the lowest paying contract
+ if (total < lowest_TC_threshold)
lowest_paying_contract.contract.payout_bonus += (lowest_TC_threshold - total)
/datum/contractor_item
var/name // Name of item
var/desc // description of item
var/item // item path, no item path means the purchase needs it's own handle_purchase()
- var/item_icon = "fa-broadcast-tower" // fontawesome icon to use inside the hub - https://fontawesome.com/icons/
+ var/item_icon = "broadcast-tower" // fontawesome icon to use inside the hub - https://fontawesome.com/icons/
var/limited = -1 // Any number above 0 for how many times it can be bought in a round for a single traitor. -1 is unlimited.
var/cost // Cost of the item in contract rep.
/datum/contractor_item/contract_reroll
name = "Contract Reroll"
desc = "Request a reroll of your current contract list. Will generate a new target, payment, and dropoff for the contracts you currently have available."
- item_icon = "fa-dice"
+ item_icon = "dice"
limited = 2
cost = 0
/datum/contractor_item/contract_reroll/handle_purchase(var/datum/contractor_hub/hub)
. = ..()
if (.)
- var/list/new_target_list = list() // We're not regenerating already completed/aborted/extracting contracts, but we don't want to repeat their targets.
+ /// We're not regenerating already completed/aborted/extracting contracts, but we don't want to repeat their targets.
+ var/list/new_target_list = list()
for(var/datum/syndicate_contract/contract_check in hub.assigned_contracts)
if (contract_check.status != CONTRACT_STATUS_ACTIVE && contract_check.status != CONTRACT_STATUS_INACTIVE)
if (contract_check.contract.target)
new_target_list.Add(contract_check.contract.target)
continue
- for(var/datum/syndicate_contract/rerolling_contract in hub.assigned_contracts) // Reroll contracts without duplicates
+ /// Reroll contracts without duplicates
+ for(var/datum/syndicate_contract/rerolling_contract in hub.assigned_contracts)
if (rerolling_contract.status != CONTRACT_STATUS_ACTIVE && rerolling_contract.status != CONTRACT_STATUS_INACTIVE)
continue
rerolling_contract.generate(new_target_list)
new_target_list.Add(rerolling_contract.contract.target)
- hub.assigned_targets = new_target_list // Set our target list with the new set we've generated.
+ /// Set our target list with the new set we've generated.
+ hub.assigned_targets = new_target_list
/datum/contractor_item/contractor_pinpointer
name = "Contractor Pinpointer"
desc = "A pinpointer that finds targets even without active suit sensors. Due to taking advantage of an exploit within the system, it can't pinpoint to the same accuracy as the traditional models. Becomes permanently locked to the user that first activates it."
@@ -125,20 +143,25 @@
/datum/contractor_item/contractor_partner/handle_purchase(var/datum/contractor_hub/hub, mob/living/user)
. = ..()
+
if (.)
to_chat(user, "The uplink vibrates quietly, connecting to nearby agents...")
- var/list/mob/candidates = pollGhostCandidates("Do you want to play as the Contractor Support Unit for [user.real_name]?", ROLE_PAI, null, FALSE, 100, POLL_IGNORE_CONTRACTOR_SUPPORT)
+
+ var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the Contractor Support Unit for [user.real_name]?", ROLE_PAI, null, FALSE, 100, POLL_IGNORE_CONTRACTOR_SUPPORT)
+
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
spawn_contractor_partner(user, C.key)
else
to_chat(user, "No available agents at this time, please try again later.")
- limited += 1 // refund and add the limit back.
+ // refund and add the limit back.
+ limited += 1
hub.contract_rep += cost
hub.purchased_items -= src
/datum/outfit/contractor_partner
name = "Contractor Support Unit"
+
uniform = /obj/item/clothing/under/chameleon
suit = /obj/item/clothing/suit/chameleon
back = /obj/item/storage/backpack
@@ -148,28 +171,35 @@
ears = /obj/item/radio/headset/chameleon
id = /obj/item/card/id/syndicate
r_hand = /obj/item/storage/toolbox/syndicate
+
backpack_contents = list(/obj/item/storage/box/survival, /obj/item/implanter/uplink, /obj/item/clothing/mask/chameleon,
/obj/item/storage/fancy/cigarettes/cigpack_syndicate, /obj/item/lighter)
/datum/outfit/contractor_partner/post_equip(mob/living/carbon/human/H, visualsOnly)
. = ..()
- var/obj/item/clothing/mask/cigarette/syndicate/cig = H.get_item_by_slot(SLOT_WEAR_MASK)
- cig.light() // pre-light their cig for extra badass
+ var/obj/item/clothing/mask/cigarette/syndicate/cig = H.get_item_by_slot(ITEM_SLOT_MASK)
+ // pre-light their cig
+ cig.light()
/datum/contractor_item/contractor_partner/proc/spawn_contractor_partner(mob/living/user, key)
var/mob/living/carbon/human/partner = new()
var/datum/outfit/contractor_partner/partner_outfit = new()
+
partner_outfit.equip(partner)
+
var/obj/structure/closet/supplypod/arrival_pod = new()
+
arrival_pod.style = STYLE_SYNDICATE
arrival_pod.explosionSize = list(0,0,0,1)
arrival_pod.bluespace = TRUE
var/turf/free_location = find_obstruction_free_location(2, user)
- if (!free_location) // We really want to send them - if we can't find a nice location just land it on top of them.
+ // We really want to send them - if we can't find a nice location just land it on top of them.
+ if (!free_location)
free_location = get_turf(user)
partner.forceMove(arrival_pod)
partner.ckey = key
- partner_mind = partner.mind // We give a reference to the mind that'll be the support unit
+ /// We give a reference to the mind that'll be the support unit
+ partner_mind = partner.mind
partner_mind.make_Contractor_Support()
to_chat(partner_mind.current, "\n[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.")
to_chat(partner_mind.current, "Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.\n\n")
@@ -186,7 +216,7 @@
. = ..()
if (.)
power_fail(35, 50)
- priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", "poweroff")
+ priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", "poweroff.ogg")
// Subtract cost, and spawn if it's an item.
/datum/contractor_item/proc/handle_purchase(var/datum/contractor_hub/hub, mob/living/user)
@@ -199,6 +229,7 @@
else if (limited == 0)
return FALSE
hub.purchased_items.Add(src)
+ user.playsound_local(user, 'sound/machines/uplinkpurchase.ogg', 100)
if (item && ispath(item))
var/atom/item_to_create = new item(get_turf(user))
diff --git a/code/modules/antagonists/traitor/syndicate_contract.dm b/code/modules/antagonists/traitor/syndicate_contract.dm
index 70ff59eee0..0f67616a32 100644
--- a/code/modules/antagonists/traitor/syndicate_contract.dm
+++ b/code/modules/antagonists/traitor/syndicate_contract.dm
@@ -4,47 +4,70 @@
var/datum/objective/contract/contract = new()
var/target_rank
var/ransom = 0
- var/payout_type = null
+ var/payout_type
+ var/wanted_message
+
var/list/victim_belongings = list()
/datum/syndicate_contract/New(contract_owner, blacklist, type=CONTRACT_PAYOUT_SMALL)
contract.owner = contract_owner
payout_type = type
+
generate(blacklist)
/datum/syndicate_contract/proc/generate(blacklist)
contract.find_target(null, blacklist)
- var/datum/data/record/record = find_record("name", contract.target.name, GLOB.data_core.general)
- if(record)
+
+ var/datum/data/record/record
+ if (contract.target)
+ record = find_record("name", contract.target.name, GLOB.data_core.general)
+
+ if (record)
target_rank = record.fields["rank"]
else
target_rank = "Unknown"
+
if (payout_type == CONTRACT_PAYOUT_LARGE)
contract.payout_bonus = rand(9,13)
- else if(payout_type == CONTRACT_PAYOUT_MEDIUM)
+ else if (payout_type == CONTRACT_PAYOUT_MEDIUM)
contract.payout_bonus = rand(6,8)
else
contract.payout_bonus = rand(2,4)
+
contract.payout = rand(0, 2)
contract.generate_dropoff()
+
ransom = 100 * rand(18, 45)
+ var/base = pick_list(WANTED_FILE, "basemessage")
+ var/verb_string = pick_list(WANTED_FILE, "verb")
+ var/noun = pick_list_weighted(WANTED_FILE, "noun")
+ var/location = pick_list_weighted(WANTED_FILE, "location")
+ wanted_message = "[base] [verb_string] [noun] [location]."
+
/datum/syndicate_contract/proc/handle_extraction(var/mob/living/user)
if (contract.target && contract.dropoff_check(user, contract.target.current))
+
var/turf/free_location = find_obstruction_free_location(3, user, contract.dropoff)
- if(free_location) // We've got a valid location, launch.
+
+ if (free_location)
+ // We've got a valid location, launch.
launch_extraction_pod(free_location)
return TRUE
+
return FALSE
// Launch the pod to collect our victim.
/datum/syndicate_contract/proc/launch_extraction_pod(turf/empty_pod_turf)
var/obj/structure/closet/supplypod/extractionpod/empty_pod = new()
+
RegisterSignal(empty_pod, COMSIG_ATOM_ENTERED, .proc/enter_check)
+
empty_pod.stay_after_drop = TRUE
empty_pod.reversing = TRUE
empty_pod.explosionSize = list(0,0,0,1)
empty_pod.leavingSound = 'sound/effects/podwoosh.ogg'
+
new /obj/effect/abstract/DPtarget(empty_pod_turf, empty_pod)
/datum/syndicate_contract/proc/enter_check(datum/source, sent_mob)
@@ -52,37 +75,55 @@
if(isliving(sent_mob))
var/mob/living/M = sent_mob
var/datum/antagonist/traitor/traitor_data = contract.owner.has_antag_datum(/datum/antagonist/traitor)
+
if(M == contract.target.current)
traitor_data.contractor_hub.contract_TC_to_redeem += contract.payout
+ traitor_data.contractor_hub.contracts_completed += 1
+
if(M.stat != DEAD)
traitor_data.contractor_hub.contract_TC_to_redeem += contract.payout_bonus
+
status = CONTRACT_STATUS_COMPLETE
+
if(traitor_data.contractor_hub.current_contract == src)
traitor_data.contractor_hub.current_contract = null
+
traitor_data.contractor_hub.contract_rep += 2
else
status = CONTRACT_STATUS_ABORTED // Sending a target that wasn't even yours is as good as just aborting it
+
if(traitor_data.contractor_hub.current_contract == src)
traitor_data.contractor_hub.current_contract = null
+
if(iscarbon(M))
for(var/obj/item/W in M)
if(ishuman(M))
var/mob/living/carbon/human/H = M
- if(W == H.w_uniform || W == H.shoes)
- continue //So all they're left with are shoes and uniform.
+ if(W == H.w_uniform)
+ continue //So all they're left with are shoes and uniform.
+ if(W == H.shoes)
+ continue
+
+
M.transferItemToLoc(W)
victim_belongings.Add(W)
+
var/obj/structure/closet/supplypod/extractionpod/pod = source
- pod.send_up(pod) // Handle the pod returning
+
+ // Handle the pod returning
+ pod.send_up(pod)
+
if(ishuman(M))
- var/mob/living/carbon/human/target = M // After we remove items, at least give them what they need to live.
+ var/mob/living/carbon/human/target = M
+
+ // After we remove items, at least give them what they need to live.
target.dna.species.give_important_for_life(target)
handleVictimExperience(M) // After pod is sent we start the victim narrative/heal.
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
var/points_to_check = min(D.account_balance, ransom)
D.adjust_money(min(points_to_check, ransom))
priority_announce("One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. \
- As is policy we've taken a portion of the station's funds to offset the overall cost.", null, "attention", null, "Nanotrasen Asset Protection")
+ As is policy we've taken a portion of the station's funds to offset the overall cost.", null, "attention", null, "Nanotrasen Asset Protection")
sleep(30)
@@ -128,13 +169,18 @@
M.Dizzy(15)
M.confused += 20
-/datum/syndicate_contract/proc/returnVictim(var/mob/living/M) // We're returning the victim
+// We're returning the victim
+/datum/syndicate_contract/proc/returnVictim(var/mob/living/M)
var/list/possible_drop_loc = list()
+
for(var/turf/possible_drop in contract.dropoff.contents)
- if(!is_blocked_turf(possible_drop))
- possible_drop_loc.Add(possible_drop)
+ if(!isspaceturf(possible_drop) && !isclosedturf(possible_drop))
+ if(!is_blocked_turf(possible_drop))
+ possible_drop_loc.Add(possible_drop)
+
if(possible_drop_loc.len > 0)
var/pod_rand_loc = rand(1, possible_drop_loc.len)
+
var/obj/structure/closet/supplypod/return_pod = new()
return_pod.bluespace = TRUE
return_pod.explosionSize = list(0,0,0,0)
@@ -144,8 +190,10 @@
for(var/obj/item/W in M)
if(ishuman(M))
var/mob/living/carbon/human/H = M
- if(W == H.w_uniform || W == H.shoes)
+ if(W == H.w_uniform)
continue //So all they're left with are shoes and uniform.
+ if(W == H.shoes)
+ continue
M.dropItemToGround(W)
for(var/obj/item/W in victim_belongings)
W.forceMove(return_pod)
diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm
index 2701a2d006..d8a8e9a1be 100644
--- a/code/modules/antagonists/wizard/equipment/artefact.dm
+++ b/code/modules/antagonists/wizard/equipment/artefact.dm
@@ -259,7 +259,7 @@
to_chat(target, "You suddenly feel very hot")
target.adjust_bodytemperature(50)
GiveHint(target)
- else if(is_pointed(I))
+ else if(I.get_sharpness() == SHARP_POINTY)
to_chat(target, "You feel a stabbing pain in [parse_zone(user.zone_selected)]!")
target.DefaultCombatKnockdown(40)
GiveHint(target)
diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm
index ab043ef1c9..d4d117f8ee 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook.dm
@@ -294,6 +294,11 @@
dat += "[surplus] left. "
return dat
+/datum/spellbook_entry/item/timestop_katana
+ name = "Temporal Katana"
+ desc = "An oddly-weighted katana, reinforced to allow parrying, with a temporal anomaly magically shoved into it. Successful ripostes prove devastating to those unprepared."
+ item_path = /obj/item/katana/timestop
+
/datum/spellbook_entry/item/staffchange
name = "Staff of Change"
desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself."
@@ -503,6 +508,7 @@
name = "Summon Guns"
desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. Just be careful not to stand still too long!"
dynamic_requirement = 60
+ limit = 1
/datum/spellbook_entry/summon/guns/IsAvailible()
if(!SSticker.mode) // In case spellbook is placed on map
@@ -521,6 +527,7 @@
name = "Summon Magic"
desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time."
dynamic_requirement = 60
+ limit = 1
/datum/spellbook_entry/summon/magic/IsAvailible()
if(!SSticker.mode) // In case spellbook is placed on map
diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm
index 70adafd3fb..7263793f7f 100644
--- a/code/modules/antagonists/wizard/wizard.dm
+++ b/code/modules/antagonists/wizard/wizard.dm
@@ -13,6 +13,7 @@
var/move_to_lair = TRUE
var/outfit_type = /datum/outfit/wizard
var/wiz_age = WIZARD_AGE_MIN /* Wizards by nature cannot be too young. */
+ show_to_ghosts = TRUE
/datum/antagonist/wizard/on_gain()
register()
diff --git a/code/modules/antagonists/xeno/xeno.dm b/code/modules/antagonists/xeno/xeno.dm
index 7c4c5351df..2cc8e34b99 100644
--- a/code/modules/antagonists/xeno/xeno.dm
+++ b/code/modules/antagonists/xeno/xeno.dm
@@ -12,6 +12,7 @@
name = "Xenomorph"
job_rank = ROLE_ALIEN
show_in_antagpanel = FALSE
+ show_to_ghosts = TRUE
var/datum/team/xeno/xeno_team
threat = 3
diff --git a/code/modules/arousal/arousal.dm b/code/modules/arousal/arousal.dm
index c9b2690784..4a48e0598d 100644
--- a/code/modules/arousal/arousal.dm
+++ b/code/modules/arousal/arousal.dm
@@ -44,6 +44,7 @@
if(G.genital_flags & GENITAL_CAN_AROUSE && !G.aroused_state && prob(strength*G.sensitivity))
G.set_aroused_state(strength > 0)
G.update_appearance()
+ update_body(TRUE)
if(G.aroused_state)
genit_list += G
return genit_list
@@ -70,6 +71,12 @@
R.trans_to(target, R.total_volume * (spill ? G.fluid_transfer_factor : 1))
G.time_since_last_orgasm = 0
R.clear_reagents()
+ //skyrat edit - chock i am going to beat you to death
+ //this is not a joke i am actually going to break your
+ //ribcage
+ if(!Process_Spacemove(turn(dir, 180)))
+ newtonian_move(turn(dir, 180))
+ //
/mob/living/carbon/human/proc/mob_climax_outside(obj/item/organ/genital/G, mb_time = 30) //This is used for forced orgasms and other hands-free climaxes
var/datum/reagents/fluid_source = G.climaxable(src, TRUE)
@@ -189,7 +196,8 @@
return TRUE
//Here's the main proc itself
-/mob/living/carbon/human/proc/mob_climax(forced_climax=FALSE) //Forced is instead of the other proc, makes you cum if you have the tools for it, ignoring restraints
+//skyrat edit - forced partner and spillage
+/mob/living/carbon/human/proc/mob_climax(forced_climax=FALSE, var/mob/living/forced_partner = null, var/forced_spillage = TRUE) //Forced is instead of the other proc, makes you cum if you have the tools for it, ignoring restraints
if(mb_cd_timer > world.time)
if(!forced_climax) //Don't spam the message to the victim if forced to come too fast
to_chat(src, "You need to wait [DisplayTimeText((mb_cd_timer - world.time), TRUE)] before you can do that again!")
@@ -224,8 +232,17 @@
partner = C
else //A cat is fine too
partner = check_target
+ //skyrat edit
+ if(forced_partner)
+ if((forced_partner == TRUE) || (!istype(forced_partner)))
+ partner = null
+ else
+ partner = forced_partner
+ //
if(partner) //Did they pass the clothing checks?
- mob_climax_partner(G, partner, mb_time = 0) //Instant climax due to forced
+ //skyrat edit
+ mob_climax_partner(G, partner, spillage = forced_spillage, mb_time = 0) //Instant climax due to forced
+ //
continue //You've climaxed once with this organ, continue on
//not exposed OR if no partner was found while exposed, climax alone
mob_climax_outside(G, mb_time = 0) //removed climax timer for sudden, forced orgasms
diff --git a/code/modules/arousal/genitals.dm b/code/modules/arousal/genitals.dm
index 4d2e5e6fef..d1b9410bee 100644
--- a/code/modules/arousal/genitals.dm
+++ b/code/modules/arousal/genitals.dm
@@ -162,7 +162,13 @@
/obj/item/organ/genital/proc/generate_fluid(datum/reagents/R)
var/amount = clamp(fluid_rate * time_since_last_orgasm * fluid_mult,0,fluid_max_volume)
R.clear_reagents()
- R.add_reagent(fluid_id,amount)
+ //skyrat edit - fix coom
+ if(fluid_id)
+ R.add_reagent(fluid_id,amount)
+ else if(linked_organ && linked_organ.fluid_id)
+ amount = clamp(linked_organ.fluid_rate * time_since_last_orgasm * linked_organ.fluid_mult,0,linked_organ.fluid_max_volume)
+ R.add_reagent(linked_organ.fluid_id, amount)
+ //
return TRUE
/obj/item/organ/genital/proc/update_link()
@@ -232,23 +238,6 @@
/obj/item/organ/genital/proc/get_features(mob/living/carbon/human/H)
return
-
-//procs to handle sprite overlays being applied to humans
-
-/mob/living/carbon/human/equip_to_slot(obj/item/I, slot)
- . = ..()
- if(!. && I && slot && !(slot in GLOB.no_genitals_update_slots)) //the item was successfully equipped, and the chosen slot wasn't merely storage, hands or cuffs.
- update_genitals()
-
-/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE)
- var/no_update = FALSE
- if(!I || I == l_store || I == r_store || I == s_store || I == handcuffed || I == legcuffed || get_held_index_of_item(I)) //stops storages, cuffs and held items from triggering it.
- no_update = TRUE
- . = ..()
- if(!. || no_update)
- return
- update_genitals()
-
/mob/living/carbon/human/proc/update_genitals()
if(QDELETED(src))
return
diff --git a/code/modules/arousal/organs/breasts.dm b/code/modules/arousal/organs/breasts.dm
index 0989966638..9e30530ad3 100644
--- a/code/modules/arousal/organs/breasts.dm
+++ b/code/modules/arousal/organs/breasts.dm
@@ -134,4 +134,4 @@
toggle_visibility(D.features["breasts_visibility"], FALSE)
#undef BREASTS_ICON_MIN_SIZE
-#undef BREASTS_ICON_MAX_SIZE
\ No newline at end of file
+#undef BREASTS_ICON_MAX_SIZE
diff --git a/code/modules/arousal/organs/penis.dm b/code/modules/arousal/organs/penis.dm
index 7b20c0e7ce..5a451359f3 100644
--- a/code/modules/arousal/organs/penis.dm
+++ b/code/modules/arousal/organs/penis.dm
@@ -106,4 +106,4 @@
diameter_ratio = D.features["cock_diameter_ratio"]
shape = D.features["cock_shape"]
prev_length = length
- toggle_visibility(D.features["cock_visibility"], FALSE)
\ No newline at end of file
+ toggle_visibility(D.features["cock_visibility"], FALSE)
diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm
index 36c444f02d..1c814fa193 100644
--- a/code/modules/assembly/bomb.dm
+++ b/code/modules/assembly/bomb.dm
@@ -62,6 +62,7 @@
/obj/item/onetankbomb/analyzer_act(mob/living/user, obj/item/I)
bombtank.analyzer_act(user, I)
+ return TRUE
/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly
bombassembly.attack_self(user, TRUE)
@@ -90,7 +91,7 @@
if(bombassembly)
bombassembly.on_found(finder)
-/obj/item/onetankbomb/attack_hand() //also for mousetraps
+/obj/item/onetankbomb/on_attack_hand() //also for mousetraps
. = ..()
if(.)
return
diff --git a/code/modules/assembly/health.dm b/code/modules/assembly/health.dm
index cddc4fb08f..0af6c85fb6 100644
--- a/code/modules/assembly/health.dm
+++ b/code/modules/assembly/health.dm
@@ -4,7 +4,6 @@
icon_state = "health"
custom_materials = list(/datum/material/iron=800, /datum/material/glass=200)
attachable = TRUE
- secured = FALSE
var/scanning = FALSE
var/health_scan
@@ -12,7 +11,8 @@
/obj/item/assembly/health/examine(mob/user)
. = ..()
- . += "Use a multitool to swap between \"detect death\" mode and \"detect critical state\" mode."
+ . += "Use it in hand to turn it off/on and Alt-click to swap between \"detect death\" mode and \"detect critical state\" mode."
+ . += "[src.scanning ? "The sensor is on and you can see [health_scan] displayed on the screen" : "The sensor is off"]."
/obj/item/assembly/health/activate()
if(!..())
@@ -30,14 +30,13 @@
update_icon()
return secured
-/obj/item/assembly/health/multitool_act(mob/living/user, obj/item/I)
+/obj/item/assembly/health/AltClick(mob/living/user)
if(alarm_health == HEALTH_THRESHOLD_CRIT)
alarm_health = HEALTH_THRESHOLD_DEAD
to_chat(user, "You toggle [src] to \"detect death\" mode.")
else
alarm_health = HEALTH_THRESHOLD_CRIT
to_chat(user, "You toggle [src] to \"detect critical state\" mode.")
- return TRUE
/obj/item/assembly/health/process()
if(!scanning || !secured)
@@ -46,7 +45,6 @@
var/atom/A = src
if(connected && connected.holder)
A = connected.holder
-
for(A, A && !ismob(A), A=A.loc);
// like get_turf(), but for mobs.
var/mob/living/M = A
@@ -71,36 +69,7 @@
STOP_PROCESSING(SSobj, src)
return
-/obj/item/assembly/health/ui_interact(mob/user as mob)//TODO: Change this to the wires thingy
+/obj/item/assembly/health/attack_self(mob/user)
. = ..()
- if(!secured)
- user.show_message("The [name] is unsecured!")
- return FALSE
- var/dat = "Health Sensor"
- dat += " [scanning?"On":"Off"]"
- if(scanning && health_scan)
- dat += " Health: [health_scan]"
- user << browse(dat, "window=hscan")
- onclose(user, "hscan")
-
-/obj/item/assembly/health/Topic(href, href_list)
- ..()
- if(!ismob(usr))
- return
-
- var/mob/user = usr
-
- if(!user.canUseTopic(src))
- usr << browse(null, "window=hscan")
- onclose(usr, "hscan")
- return
-
- if(href_list["scanning"])
- toggle_scan()
-
- if(href_list["close"])
- usr << browse(null, "window=hscan")
- return
-
- attack_self(user)
- return
+ to_chat(user, "You toggle [src] [src.scanning ? "off" : "on"].")
+ toggle_scan()
diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm
index c960a7f039..bf56449a0c 100644
--- a/code/modules/assembly/holder.dm
+++ b/code/modules/assembly/holder.dm
@@ -87,7 +87,7 @@
if(a_right)
a_right.dropped(user)
-/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess
+/obj/item/assembly_holder/on_attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess
. = ..()
if(.)
return
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index 33c6d46045..899eb12511 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -4,7 +4,6 @@
icon_state = "infrared"
custom_materials = list(/datum/material/iron=1000, /datum/material/glass=500)
is_position_sensitive = TRUE
-
var/on = FALSE
var/visible = FALSE
var/maxlength = 8
@@ -38,7 +37,7 @@
/obj/item/assembly/infra/activate()
if(!..())
- return FALSE//Cooldown check
+ return FALSE //Cooldown check
on = !on
refreshBeam()
update_icon()
@@ -69,7 +68,7 @@
holder.update_icon()
return
-/obj/item/assembly/infra/dropped(mob/user)
+/obj/item/assembly/infra/dropped()
. = ..()
if(holder)
holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder
@@ -124,7 +123,7 @@
return
refreshBeam()
-/obj/item/assembly/infra/attack_hand()
+/obj/item/assembly/infra/on_attack_hand()
. = ..()
refreshBeam()
@@ -133,7 +132,7 @@
. = ..()
setDir(t)
-/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback)
+/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
. = ..()
olddir = dir
@@ -176,55 +175,55 @@
return
return refreshBeam()
-/obj/item/assembly/infra/ui_interact(mob/user)//TODO: change this this to the wire control panel
- . = ..()
- if(is_secured(user))
- user.set_machine(src)
- var/dat = "Infrared Laser"
- dat += " Status: [on ? "On" : "Off"]"
- dat += " Visibility: [visible ? "Visible" : "Invisible"]"
- dat += "