From 1cc3df9cd330cd3f4dc98be0afac7d9501300837 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 22:46:33 +0200 Subject: [PATCH 01/78] Fake moustaches are now poorly slapped on top of what you're wearing [MDB IGNORE] (#24135) * Fake moustaches are now poorly slapped on top of what you're wearing (#78776) Another "+1 line changed" banger ![moustache](https://github.com/tgstation/tgstation/assets/68878861/8f0cc5ee-97a5-4a64-8ac5-ae3b387760e6) ## About The Pull Request Bumps up the worn layer of fake moustache and it's italian subtype to 4. Note that NOMASK inv flag will still prevent them from appearing. ## Why It's Good For The Game - Paper-thin disguises amuse me - Chef fashion - Groucho maxxing - A victory for good taste ![obraz](https://github.com/tgstation/tgstation/assets/68878861/5f792a8e-5bd0-40ab-a49d-095502fe9812) ## Changelog :cl: add: Fake moustaches are now poorly slapped on top of what you're wearing /:cl: * Fake moustaches are now poorly slapped on top of what you're wearing --------- Co-authored-by: Likteer <68878861+Likteer@users.noreply.github.com> --- code/modules/clothing/masks/moustache.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/modules/clothing/masks/moustache.dm b/code/modules/clothing/masks/moustache.dm index ecfd5d5e007..aaf59be51e4 100644 --- a/code/modules/clothing/masks/moustache.dm +++ b/code/modules/clothing/masks/moustache.dm @@ -2,6 +2,7 @@ name = "fake moustache" desc = "Warning: moustache is fake." icon_state = "fake-moustache" + alternate_worn_layer = ABOVE_BODY_FRONT_HEAD_LAYER w_class = WEIGHT_CLASS_TINY flags_inv = HIDEFACE species_exception = list(/datum/species/golem) From 33bb2571c99382d4b8ca7d01101bd4e6f3e4c99d Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 22:47:44 +0200 Subject: [PATCH 02/78] Bleeding has been subtly broken for 3+ years, let's fix that yeah? [MDB IGNORE] (#24136) * Bleeding has been subtly broken for 3+ years, let's fix that yeah? (#78783) ## About The Pull Request the `drip` argument was being passed an inverted value. Broke in https://github.com/tgstation/tgstation/pull/56056 ## Changelog :cl: fix: Blood once again appears as small drops instead of splatters during minor bleeding. /:cl: * Bleeding has been subtly broken for 3+ years, let's fix that yeah? --------- Co-authored-by: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> --- code/modules/mob/living/blood.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 9354f02e2b2..cbec374449c 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -94,7 +94,7 @@ //Blood loss still happens in locker, floor stays clean if(isturf(loc) && prob(sqrt(amt)*BLOOD_DRIP_RATE_MOD)) - add_splatter_floor(loc, (amt >= 10)) + add_splatter_floor(loc, (amt <= 10)) /mob/living/carbon/human/bleed(amt) amt *= physiology.bleed_mod From 2f78d2da7932fcb7740ff9599dec83ac4a81fec3 Mon Sep 17 00:00:00 2001 From: nikothedude <59709059+nikothedude@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:29:29 -0400 Subject: [PATCH 03/78] Adds synth wounds and resets synth limb damage to 1x, other synth changes (#23733) * [TEST-MERGE FIRST] Wound refactor number two: Full synthetic support * Wound refactor two compatability (#23618) * Delam emergency procedure moth (#23483) * safety moff * delta/whitespace/examine * icebox * moff poster * moff poster * Update DelamProcedure.tsx * Update scram.dm * sound plays during warning * remove nightshift, theres already a global proc * scrubber hint * missed that define * Apply suggestions from code review Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> * covered by ui_interact * Update modular_skyrat/modules/delam_emergency_stop/code/scram.dm Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> * Update modular_skyrat/modules/delam_emergency_stop/code/scram.dm --------- Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * Automatic changelog for PR #23483 [ci skip] * Automatic changelog compile [ci skip] * [non modular] disables TG "hold up" for the foreseeable future (#23607) Update gun.dm * Automatic changelog for PR #23607 [ci skip] * [MIRROR] Desouls Hivelord [MDB IGNORE] (#23609) * Desouls Hivelord (#78213) ## About The Pull Request ![dreammaker_RJz4brjobM](https://github.com/tgstation/tgstation/assets/7483112/e5e4a3e9-ea6b-47f9-887c-3339d24d3fa8) Replaces the sprite of the hivelord with a new one, in my continuing quest to annihilate the old asteroid mob sprites. A (never completed) asteroid mob resprite was actually my first PR, this one is my 200th. I am also planning on fucking with basic mob versions of these mobs some time but the sprites can be atomised out. In addition to replacing the old-ass MSPaint sprites, this PR also adds a short death animation effect to the hivelord brood (from hivelords or legions) which looks nicer than them just vanishing instantly upon death. Look at this video for an example of the animation: https://www.youtube.com/watch?v=cKaskN5-y2A ## Why It's Good For The Game Looks nicer. ## Changelog :cl: image: Hivelords have a new sprite. image: Hivelord and Legion brood have a death animation. /:cl: * Desouls Hivelord --------- Co-authored-by: Jacquerel * Automatic changelog for PR #23609 [ci skip] * [MIRROR] Updates chem factory tank sprites [MDB IGNORE] (#23608) * Updates chem factory tank sprites (#78209) Updates chem factory tank sprites. * Updates chem factory tank sprites --------- Co-authored-by: Wallem <66052067+Wallemations@users.noreply.github.com> * [MIRROR] Rice Dough can be made in a beaker [MDB IGNORE] (#23611) * Rice Dough can be made in a beaker (#78062) ## About The Pull Request Rice dough can be made in a beaker using 20u of Rice Flour and 10u of Water. 10u of Rice Flour is made from 5u of Rice and 5u of Flour. Rice dough can still be crafted manually using the crafting menu and the original recipe. ## Why It's Good For The Game Cooks can sometimes get swamped with work, especially on a high-pop shift or when there are no botanists. By making rice dough more convenient to make, cooks don't need to spend as much time in the crafting menu. Rice Flour is made from mixing equal parts Rice and Flour. Since no recipe other than Rice dough uses both Rice and Flour in it's Recipe, it should be fine to turn those regents into the intermediate reagent "Rice Flour". Fixes #77966 ## Changelog :cl: qol: Rice Dough may be made in beaker instead of being crafted, but the rice and flour must be added first /:cl: * Rice Dough can be made in a beaker --------- Co-authored-by: blueDev2 <89804215+blueDev2@users.noreply.github.com> * Automatic changelog for PR #23608 [ci skip] * Automatic changelog for PR #23611 [ci skip] * f * awda * unused type!!! * Apply suggestions from code review Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * oh hey unused defines! bye --------- Co-authored-by: lessthanthree <83487515+lessthnthree@users.noreply.github.com> Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> Co-authored-by: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Co-authored-by: Changelogs Co-authored-by: RatFromTheJungle <62520989+RatFromTheJungle@users.noreply.github.com> Co-authored-by: Jacquerel Co-authored-by: Wallem <66052067+Wallemations@users.noreply.github.com> Co-authored-by: blueDev2 <89804215+blueDev2@users.noreply.github.com> * aa * fsegf * im confused * ASSwdadzxd * a * break to fix shuytyyhdg * aaaasdfggh * a * ack * a little more * AA * a readme * a lil colder * another touchup * ack * msucle * a * one * tada * ack * a * fuck! * some packs and medicine * a * more * it only makes sense * readme update * woo * and now, we map it in * me when * Apply suggestions from code review Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> * j * fix ci * Apply suggestions from code review Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> * o;l * SPACE * AAAAAAAAAAAAAAAAAAAAAH * Apply suggestions from code review Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> * j * Apply suggestions from code review Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> * y * a * dwadwa * Just fixing this typo you missed * And found another instance of this same block of code * change the way burns work * thick clothing now blocks reagents * cock * uyjujy * a * yjhf * awdawdawd * remove stacking buffs + make electrical damage worse * whoops * FUCK * adwdad * AAAA * oh the misery * this'd be cool * warn * a * i think it needs a lil buff * Apply suggestions from code review Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * asd * I LOVE COMPILING AND NOT HAVING ERROSR --------- Co-authored-by: lessthanthree <83487515+lessthnthree@users.noreply.github.com> Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> Co-authored-by: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Co-authored-by: Changelogs Co-authored-by: RatFromTheJungle <62520989+RatFromTheJungle@users.noreply.github.com> Co-authored-by: Jacquerel Co-authored-by: Wallem <66052067+Wallemations@users.noreply.github.com> Co-authored-by: blueDev2 <89804215+blueDev2@users.noreply.github.com> Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> --- ...avaland_surface_syndicate_base1_skyrat.dmm | 2 + .../SpaceRuins/skyrat/interdynefob.dmm | 2 + code/__DEFINES/text.dm | 4 + code/__DEFINES/wounds.dm | 24 +- .../subsystem/processing/quirks.dm | 2 +- .../mob/living/carbon/carbon_defense.dm | 6 +- .../reagents/cat2_medicine_reagents.dm | 13 +- .../code/modules/jobs/job_types/roboticist.dm | 16 + .../modules/medical/attributions.txt | 2 + .../modules/medical/code/cargo/packs.dm | 36 + modular_skyrat/modules/medical/code/medkit.dm | 177 +++++ modular_skyrat/modules/medical/code/sprays.dm | 12 + .../modules/medical/code/wounds/muscle.dm | 19 +- .../code/wounds/synth/blunt/robotic_blunt.dm | 398 +++++++++++ .../wounds/synth/blunt/robotic_blunt_T1.dm | 65 ++ .../wounds/synth/blunt/robotic_blunt_T2.dm | 52 ++ .../wounds/synth/blunt/robotic_blunt_T3.dm | 385 +++++++++++ .../wounds/synth/blunt/secures_internals.dm | 388 +++++++++++ .../code/wounds/synth/robotic_burns.dm | 441 ++++++++++++ .../code/wounds/synth/robotic_muscle.dm | 47 ++ .../code/wounds/synth/robotic_pierce.dm | 146 ++++ .../code/wounds/synth/robotic_slash.dm | 628 ++++++++++++++++++ .../medical/code/wounds/wound_effects.dm | 33 + modular_skyrat/modules/medical/readme.md | 51 ++ .../medical/sound/robotic_slash_T1.ogg | Bin 0 -> 34541 bytes .../medical/sound/robotic_slash_T2.ogg | Bin 0 -> 50347 bytes .../medical/sound/robotic_slash_T3.ogg | Bin 0 -> 63099 bytes .../modules/modular_vending/code/wardrobes.dm | 3 + .../modules/synths/code/bodyparts/limbs.dm | 4 +- .../modules/synths/code/reagents/pill.dm | 6 +- .../modules/synths/code/species/synthetic.dm | 4 +- strings/wounds/metal_scar_desc.json | 67 ++ tgstation.dme | 13 + 33 files changed, 3018 insertions(+), 28 deletions(-) create mode 100644 modular_skyrat/modules/medical/attributions.txt create mode 100644 modular_skyrat/modules/medical/code/cargo/packs.dm create mode 100644 modular_skyrat/modules/medical/code/medkit.dm create mode 100644 modular_skyrat/modules/medical/code/sprays.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T1.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T2.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T3.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/synth/blunt/secures_internals.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/synth/robotic_burns.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/synth/robotic_muscle.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/synth/robotic_pierce.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/synth/robotic_slash.dm create mode 100644 modular_skyrat/modules/medical/code/wounds/wound_effects.dm create mode 100644 modular_skyrat/modules/medical/readme.md create mode 100644 modular_skyrat/modules/medical/sound/robotic_slash_T1.ogg create mode 100644 modular_skyrat/modules/medical/sound/robotic_slash_T2.ogg create mode 100644 modular_skyrat/modules/medical/sound/robotic_slash_T3.ogg create mode 100644 strings/wounds/metal_scar_desc.json diff --git a/_maps/RandomRuins/LavaRuins/skyrat/lavaland_surface_syndicate_base1_skyrat.dmm b/_maps/RandomRuins/LavaRuins/skyrat/lavaland_surface_syndicate_base1_skyrat.dmm index 957a669e53c..b9f9c542350 100644 --- a/_maps/RandomRuins/LavaRuins/skyrat/lavaland_surface_syndicate_base1_skyrat.dmm +++ b/_maps/RandomRuins/LavaRuins/skyrat/lavaland_surface_syndicate_base1_skyrat.dmm @@ -1741,6 +1741,8 @@ /obj/effect/turf_decal/tile/blue{ dir = 4 }, +/obj/structure/table/reinforced, +/obj/item/storage/backpack/duffelbag/synth_treatment_kit/trauma/advanced/unzipped, /turf/open/floor/iron/white, /area/ruin/syndicate_lava_base/medbay) "rQ" = ( diff --git a/_maps/RandomRuins/SpaceRuins/skyrat/interdynefob.dmm b/_maps/RandomRuins/SpaceRuins/skyrat/interdynefob.dmm index 464498600fd..0e37692de27 100644 --- a/_maps/RandomRuins/SpaceRuins/skyrat/interdynefob.dmm +++ b/_maps/RandomRuins/SpaceRuins/skyrat/interdynefob.dmm @@ -1891,6 +1891,8 @@ /obj/effect/turf_decal/trimline/dark_blue/filled/line, /obj/effect/turf_decal/siding/dark, /obj/structure/extinguisher_cabinet/directional/west, +/obj/structure/table/glass, +/obj/item/storage/backpack/duffelbag/synth_treatment_kit/trauma/advanced/unzipped, /turf/open/floor/iron/white/diagonal, /area/ruin/space/has_grav/skyrat/interdynefob/medbay) "hZ" = ( diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index 751fe7c2b58..a0df5e24eb7 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -84,6 +84,10 @@ #define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json" /// File location for bone wound descriptions #define BONE_SCAR_FILE "wounds/bone_scar_desc.json" +// SKYRAT EDIT ADDITION BEGIN - SYNTH WOUNDS +/// File location for metalic wound descriptions +#define METAL_SCAR_FILE "wounds/metal_scar_desc.json" +// SKYRAT EDIT ADDITION END /// File location for scar wound descriptions #define SCAR_LOC_FILE "wounds/scar_loc.json" /// File location for exodrone descriptions diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm index 19ccd843dd4..14067fbcd6a 100644 --- a/code/__DEFINES/wounds.dm +++ b/code/__DEFINES/wounds.dm @@ -124,10 +124,15 @@ GLOBAL_LIST_INIT(bio_state_anatomy, list( /// Generic loss wounds. See loss.dm #define WOUND_SERIES_LOSS_BASIC "wound_series_loss_basic" -// SKYRAT EDIT ADDITION BEGIN - MUSCLE WOUNDS +// SKYRAT EDIT ADDITION BEGIN - MUSCLE AND SYNTH WOUNDS // Have to put it here so I can use it in the global list of wound series -/// See muscle.dm -#define WOUND_SERIES_MUSCLE_DAMAGE "skyrat_wound_series_muscle_damage" // We use a super high number as realistically speaking TG will never increment to this amount of wound series +/// See muscle.dm and robotic_blunt.dm +#define WOUND_SERIES_MUSCLE_DAMAGE "skyrat_wound_series_muscle_damage" + +#define WOUND_SERIES_METAL_BLUNT_BASIC "wound_series_metal_blunt_basic" +#define WOUND_SERIES_METAL_BURN_OVERHEAT "wound_series_metal_burn_basic" +#define WOUND_SERIES_WIRE_SLASH_ELECTRICAL_DAMAGE "wound_series_metal_slash_electrical_damage_basic" +#define WOUND_SERIES_WIRE_PIERCE_ELECTRICAL_DAMAGE "wound_series_metal_pierce_electrical_damage_basic" // SKYRAT EDIT ADDITION END /// A assoc list of (wound typepath -> wound_pregen_data instance). Every wound should have a pregen data. @@ -203,17 +208,23 @@ GLOBAL_LIST_INIT(wounding_types_to_series, list( WOUND_BLUNT = list( WOUND_SERIES_BONE_BLUNT_BASIC, WOUND_SERIES_MUSCLE_DAMAGE, // SKYRAT EDIT -- MUSCLE WOUNDS + WOUND_SERIES_METAL_BLUNT_BASIC, // SKYRAT EDIT ADDITION - SYNTH WOUNDS + ), WOUND_SLASH = list( WOUND_SERIES_FLESH_SLASH_BLEED, - WOUND_SERIES_MUSCLE_DAMAGE, // SKYRAT EDIT -- MUSCLE WOUNDS + WOUND_SERIES_MUSCLE_DAMAGE, // SKYRAT EDIT ADDITION - MUSCLE WOUNDS + WOUND_SERIES_WIRE_SLASH_ELECTRICAL_DAMAGE, // SKYRAT EDIT ADDITION - SYNTH WOUNDS + ), WOUND_BURN = list( WOUND_SERIES_FLESH_BURN_BASIC, + WOUND_SERIES_METAL_BURN_OVERHEAT, // SKYRAT EDIT ADDITION - SYNTH WOUNDS ), WOUND_PUNCTURE = list( WOUND_SERIES_FLESH_PUNCTURE_BLEED, - WOUND_SERIES_MUSCLE_DAMAGE, // SKYRAT EDIT -- MUSCLE WOUNDS + WOUND_SERIES_MUSCLE_DAMAGE, // SKYRAT EDIT ADDITION - MUSCLE WOUNDS + WOUND_SERIES_WIRE_PIERCE_ELECTRICAL_DAMAGE, // SKYRAT EDIT ADDITION - SYNTH WOUNDS ), )) @@ -284,7 +295,8 @@ GLOBAL_LIST_INIT(wounding_types_to_series, list( /// Assoc list of biotype -> ideal scar file to be used and grab stuff from. GLOBAL_LIST_INIT(biotypes_to_scar_file, list( "[BIO_FLESH]" = FLESH_SCAR_FILE, - "[BIO_BONE]" = BONE_SCAR_FILE + "[BIO_BONE]" = BONE_SCAR_FILE, + "[BIO_METAL]" = METAL_SCAR_FILE // SKYRAT EDIT ADDITION - METAL SCARS (see robotic_blunt.dm) )) // ~burn wound infection defines diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm index 6e6fc6547e2..c34d97b28f9 100644 --- a/code/controllers/subsystem/processing/quirks.dm +++ b/code/controllers/subsystem/processing/quirks.dm @@ -15,7 +15,7 @@ GLOBAL_LIST_INIT_TYPED(quirk_blacklist, /list/datum/quirk, list( list(/datum/quirk/prosthetic_limb, /datum/quirk/quadruple_amputee, /datum/quirk/body_purist), list(/datum/quirk/prosthetic_organ, /datum/quirk/tin_man, /datum/quirk/body_purist), list(/datum/quirk/quadruple_amputee, /datum/quirk/paraplegic, /datum/quirk/hemiplegic), - list(/datum/quirk/quadruple_amputee, /datum/quirk/frail), + //list(/datum/quirk/quadruple_amputee, /datum/quirk/frail), // SKYRAT EDIT REMOVAL- Since we have synth wounds now, frail has a large downside for prosthetics and such list(/datum/quirk/social_anxiety, /datum/quirk/mute), list(/datum/quirk/mute, /datum/quirk/softspoken), list(/datum/quirk/poor_aim, /datum/quirk/bighands), diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 8cb32613da8..a51e9c3430e 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -479,8 +479,7 @@ var/immediately_stun = should_stun && !(flags & SHOCK_DELAY_STUN) if (immediately_stun) if (paralyze) - //Paralyze(40) - SKYRAT EDIT REMOVAL - StaminaKnockdown(10, TRUE) // SKYRAT EDIT ADDITION + StaminaKnockdown(stun_duration / 4) // SKYRAT EDIT CHANGE - ORIGINAL: Paralyze(40) else Knockdown(stun_duration) //Jitter and other fluff. @@ -494,8 +493,7 @@ ///Called slightly after electrocute act to apply a secondary stun. /mob/living/carbon/proc/secondary_shock(paralyze, stun_duration) if (paralyze) - //Paralyze(60) - SKYRAT EDIT REMOVAL - StaminaKnockdown(10, TRUE) //SKYRAT EDIT ADDITION + StaminaKnockdown(stun_duration / 6) // SKYRAT EDIT CHANGE - ORIGINAL: Paralyze(60) else Knockdown(stun_duration) diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm index 53558cbd820..7cad082583d 100644 --- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm @@ -203,14 +203,19 @@ inverse_chem = /datum/reagent/inverse/hercuri inverse_chem_val = 0.3 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC // SKYRAT EDIT ADDITION - Lets hercuri process in synths /datum/reagent/medicine/c2/hercuri/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() var/need_mob_update - if(affected_mob.getFireLoss() > 50) - need_mob_update = affected_mob.adjustFireLoss(-2 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) - else - need_mob_update = affected_mob.adjustFireLoss(-1.25 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) + // SKYRAT EDIT CHANGE BEGIN -- Adds check for owner_flags; indented the getFireLoss check and everything under it, so synths can get cooled down + var/owner_flags = affected_mob.dna.species.reagent_flags + if (owner_flags & PROCESS_ORGANIC) + if(affected_mob.getFireLoss() > 50) + need_mob_update = affected_mob.adjustFireLoss(-2 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) + else + need_mob_update = affected_mob.adjustFireLoss(-1.25 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) + // SKYRAT EDIT CHANGE END affected_mob.adjust_bodytemperature(rand(-25,-5) * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 50) if(ishuman(affected_mob)) var/mob/living/carbon/human/humi = affected_mob diff --git a/modular_skyrat/master_files/code/modules/jobs/job_types/roboticist.dm b/modular_skyrat/master_files/code/modules/jobs/job_types/roboticist.dm index d90c994c6ad..8c62f0a30f1 100644 --- a/modular_skyrat/master_files/code/modules/jobs/job_types/roboticist.dm +++ b/modular_skyrat/master_files/code/modules/jobs/job_types/roboticist.dm @@ -3,3 +3,19 @@ satchel = /obj/item/storage/backpack/satchel/science/robo duffelbag = /obj/item/storage/backpack/duffelbag/science/robo messenger = /obj/item/storage/backpack/messenger/science/robo + +/datum/job/roboticist + description = "Build cyborgs, mechs, AIs, and maintain them all. Create MODsuits for those that wish. Try to remind medical that you're \ + actually a lot better at treating synthetic crew members than them." + +/datum/job/roboticist/New() + . = ..() + + mail_goodies += list( + /obj/item/healthanalyzer/advanced = 15, + // if we decide later to let sci have access to the hand drill, let robos get it from here + /obj/item/storage/pill_bottle/liquid_solder = 6, + /obj/item/storage/pill_bottle/system_cleaner = 6, + /obj/item/storage/pill_bottle/nanite_slurry = 6, + /obj/item/reagent_containers/spray/hercuri/chilled = 8, + ) diff --git a/modular_skyrat/modules/medical/attributions.txt b/modular_skyrat/modules/medical/attributions.txt new file mode 100644 index 00000000000..f57c3fa0db5 --- /dev/null +++ b/modular_skyrat/modules/medical/attributions.txt @@ -0,0 +1,2 @@ +robotic_slash_T1.ogg, robotic_slash_T2.ogg, and robotic_slash_T3.ogg adapated from CGEffex's Bug Zapper2.wav (CC Attribution 4.0) +https://freesound.org/people/CGEffex/sounds/107004/ diff --git a/modular_skyrat/modules/medical/code/cargo/packs.dm b/modular_skyrat/modules/medical/code/cargo/packs.dm new file mode 100644 index 00000000000..0de1f24eb16 --- /dev/null +++ b/modular_skyrat/modules/medical/code/cargo/packs.dm @@ -0,0 +1,36 @@ +/datum/supply_pack/science/chilled_hercuri + name = "Chilled Hercuri Pack" + desc = "Contains 2 pre-chilled bottles of hercuri, 100u each. Useful for dealing with severely burnt synthetics!" + cost = CARGO_CRATE_VALUE * 2.5 + contains = list(/obj/item/reagent_containers/spray/hercuri/chilled = 2) + crate_name = "chilled hercuri crate" + + access_view = FALSE + access = FALSE + access_any = FALSE + +/datum/supply_pack/science/synth_treatment_kits + name = "Synthetic Treatment Kits" + desc = "Contains 2 treatment kits for synthetic lifeforms, filled with everything you need to treat an inorganic wound!" + cost = CARGO_CRATE_VALUE * 4.5 + contains = list(/obj/item/storage/backpack/duffelbag/synth_treatment_kit = 2) + crate_name = "synthetic treatment kits crate" + + access_view = FALSE + access = FALSE + access_any = FALSE + +/datum/supply_pack/science/synth_healing_chems + name = "Synthetic Medicine Pack" + desc = "Contains a variety of synthetic-exclusive medicine. 2 pill bottles of liquid solder, 2 of nanite slurry, 2 of system cleaner." + cost = CARGO_CRATE_VALUE * 7 // rarely made, so it should be expensive(?) + contains = list( + /obj/item/storage/pill_bottle/liquid_solder = 2, + /obj/item/storage/pill_bottle/nanite_slurry = 2, + /obj/item/storage/pill_bottle/system_cleaner = 2 + ) + crate_name = "synthetic medicine crate" + + access_view = FALSE + access = FALSE + access_any = FALSE diff --git a/modular_skyrat/modules/medical/code/medkit.dm b/modular_skyrat/modules/medical/code/medkit.dm new file mode 100644 index 00000000000..8ea40ae54bd --- /dev/null +++ b/modular_skyrat/modules/medical/code/medkit.dm @@ -0,0 +1,177 @@ +/obj/item/storage/backpack/duffelbag/synth_treatment_kit + name = "synthetic treatment kit" + desc = "A \"surgical\" duffel bag containing everything you need to treat the worst and best of inorganic wounds." + icon = 'modular_skyrat/master_files/icons/obj/clothing/backpacks.dmi' + worn_icon = 'modular_skyrat/master_files/icons/mob/clothing/back.dmi' + lefthand_file = 'modular_skyrat/master_files/icons/mob/inhands/clothing/backpack_lefthand.dmi' + righthand_file = 'modular_skyrat/master_files/icons/mob/inhands/clothing/backpack_righthand.dmi' + icon_state = "duffel_robo" + inhand_icon_state = "duffel_robo" + +/obj/item/storage/backpack/duffelbag/synth_treatment_kit/PopulateContents() // yes, this is all within the storage capacity + // Slash/Pierce wound tools - can reduce intensity of electrical damage (wires can fix generic burn damage) + new /obj/item/stack/cable_coil(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/wirecutters(src) + // Blunt/Brute tools + new /obj/item/weldingtool/largetank(src) // Used for repairing blunt damage or heating metal at T3 blunt + new /obj/item/screwdriver(src) // Used for fixing T1 blunt or securing internals of T2/3 blunt + new /obj/item/bonesetter(src) + // Clothing items + new /obj/item/clothing/head/utility/welding(src) + new /obj/item/clothing/gloves/color/black(src) // Protects from T3 mold metal step + new /obj/item/clothing/glasses/hud/diagnostic(src) // When worn, generally improves wound treatment quality + // Reagent containers + new /obj/item/reagent_containers/spray/hercuri/chilled(src) // Highly effective (specifically coded to be) against burn wounds + // Generic medical items + new /obj/item/stack/medical/gauze/twelve(src) + new /obj/item/healthanalyzer(src) + new /obj/item/healthanalyzer/simple(src) // Buffs wound treatment and gives details of wounds it scans + // "Ghetto" tools, things you shouldnt ideally use but you might have to + new /obj/item/stack/medical/bone_gel(src) // Ghetto T2/3 option for securing internals + new /obj/item/plunger(src) // Can be used to mold heated metal at T3 + +// a treatment kit with extra space and more tools/upgraded tools, like a crowbar, insuls, a reinforced plunger, a crowbar and wrench +/obj/item/storage/backpack/duffelbag/synth_treatment_kit/trauma + name = "synthetic trauma kit" + desc = "A \"surgical\" duffel bag containing everything you need to treat the worst and best of inorganic wounds. This one has extra tools and space \ + for treatment of the WORST of the worst! However, it's highly specialized interior means it can ONLY hold synthetic repair tools." + storage_type = /datum/storage/duffel/synth_trauma_kit + +/datum/storage/duffel/synth_trauma_kit + exception_max = 6 + max_slots = 27 + max_total_storage = 35 + +/datum/storage/duffel/synth_trauma_kit/New(atom/parent, max_slots, max_specific_storage, max_total_storage, numerical_stacking, allow_quick_gather, allow_quick_empty, collection_mode, attack_hand_interact) + . = ..() + + var/static/list/exception_cache = typecacheof(list( + // Mainly just stacks, with the exception of pill bottles and sprays + /obj/item/stack/cable_coil, + /obj/item/stack/medical/gauze, + /obj/item/reagent_containers/spray, + /obj/item/stack/medical/bone_gel, + /obj/item/rcd_ammo, + /obj/item/storage/pill_bottle, + )) + + var/static/list/can_hold_list = list( + // Stacks + /obj/item/stack/cable_coil, + /obj/item/stack/medical/gauze, + /obj/item/stack/medical/bone_gel, + // Reagent containers, for synth medicine + /obj/item/reagent_containers/spray, + /obj/item/storage/pill_bottle, + /obj/item/reagent_containers/pill, + /obj/item/reagent_containers/cup, + /obj/item/reagent_containers/syringe, + // Tools, including tools you might not want to use but might have to (hemostat/retractor/etc) + /obj/item/screwdriver, + /obj/item/wrench, + /obj/item/crowbar, + /obj/item/weldingtool, + /obj/item/bonesetter, + /obj/item/wirecutters, + /obj/item/hemostat, + /obj/item/retractor, + /obj/item/cautery, + /obj/item/plunger, + // RCD stuff - RCDs can easily treat the 1st step of T3 blunt + /obj/item/construction/rcd, + /obj/item/rcd_ammo, + // Clothing items + /obj/item/clothing/gloves, + /obj/item/clothing/glasses/hud/health, + /obj/item/clothing/glasses/hud/diagnostic, + /obj/item/clothing/glasses/welding, + /obj/item/clothing/glasses/sunglasses, // still provides some welding protection + /obj/item/clothing/head/utility/welding, + /obj/item/clothing/mask/gas/welding, + // Generic health items + /obj/item/healthanalyzer, + ) + exception_hold = exception_cache + + // We keep the type list and the typecache list separate... + var/static/list/can_hold_cache = typecacheof(can_hold_list) + can_hold = can_hold_cache + + //...So we can run this without it generating a line for every subtype. + can_hold_description = generate_hold_desc(can_hold_list) + +/obj/item/storage/backpack/duffelbag/synth_treatment_kit/trauma/PopulateContents() // yes, this is all within the storage capacity + // Slash/Pierce wound tools - can reduce intensity of electrical damage (wires can fix generic burn damage) + new /obj/item/stack/cable_coil(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/wirecutters(src) + // Blunt/Brute tools + new /obj/item/weldingtool/hugetank(src) // Used for repairing blunt damage or heating metal at T3 blunt + new /obj/item/screwdriver(src) // Used for fixing T1 blunt or securing internals of T2/3 blunt + new /obj/item/wrench(src) // Same as screwdriver for T2/3 + new /obj/item/crowbar(src) // Ghetto fixing option for T2/3 blunt + new /obj/item/bonesetter(src) + // Clothing items + new /obj/item/clothing/head/utility/welding(src) + new /obj/item/clothing/gloves/color/black(src) // Protects from T3 mold metal step + new /obj/item/clothing/gloves/color/yellow(src) // Protects from electrical damage and crowbarring a blunt wound + new /obj/item/clothing/glasses/hud/diagnostic(src) // When worn, generally improves wound treatment quality + // Reagent containers + new /obj/item/reagent_containers/spray/hercuri/chilled(src) // Highly effective (specifically coded to be) against burn wounds + // Generic medical items + new /obj/item/stack/medical/gauze/twelve(src) + new /obj/item/healthanalyzer(src) + new /obj/item/healthanalyzer/simple(src) // Buffs wound treatment and gives details of wounds it scans + // "Ghetto" tools, things you shouldnt ideally use but you might have to + new /obj/item/stack/medical/bone_gel(src) // Ghetto T2/3 option for securing internals + new /obj/item/plunger/reinforced(src) // Can be used to mold heated metal at T3 + +// advanced tools, an RCD, chems, etc etc. dont give this one to the crew early in the round +/obj/item/storage/backpack/duffelbag/synth_treatment_kit/trauma/advanced + name = "advanced synth trauma kit" + desc = "An \"advanced\" \"surgical\" duffel bag containing absolutely everything you need to treat the worst and best of inorganic wounds. \ + This one has extra tools and space for treatment of the ones even worse than the WORST of the worst! However, its highly specialized interior \ + means it can ONLY hold synthetic repair tools." + + storage_type = /datum/storage/duffel/synth_trauma_kit/advanced + +/datum/storage/duffel/synth_trauma_kit/advanced + exception_max = 10 + max_slots = 31 + max_total_storage = 48 + +/obj/item/storage/backpack/duffelbag/synth_treatment_kit/trauma/advanced/PopulateContents() // yes, this is all within the storage capacity + // Slash/Pierce wound tools - can reduce intensity of electrical damage (wires can fix generic burn damage) + new /obj/item/stack/cable_coil(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/crowbar/power(src) // jaws of life - wirecutters and crowbar + // Blunt/Brute tools + new /obj/item/weldingtool/experimental(src) // Used for repairing blunt damage or heating metal at T3 blunt + new /obj/item/screwdriver/power(src) // drill - screwdriver and wrench + new /obj/item/construction/rcd/loaded(src) // lets you instantly heal T3 blunt step 1 + new /obj/item/bonesetter(src) + // Clothing items + new /obj/item/clothing/head/utility/welding(src) + new /obj/item/clothing/gloves/combat(src) // insulated AND heat-resistant + new /obj/item/clothing/glasses/hud/diagnostic(src) // When worn, generally improves wound treatment quality + // Reagent containers + new /obj/item/reagent_containers/spray/hercuri/chilled(src) // Highly effective (specifically coded to be) against burn wounds + new /obj/item/reagent_containers/spray/hercuri/chilled(src) // 2 of them + new /obj/item/storage/pill_bottle/nanite_slurry(src) // Heals blunt/burn + new /obj/item/storage/pill_bottle/liquid_solder(src) // Heals brain damage + new /obj/item/storage/pill_bottle/system_cleaner(src) // Heals toxin damage and purges chems + // Generic medical items + new /obj/item/stack/medical/gauze/twelve(src) + new /obj/item/healthanalyzer/advanced(src) // advanced, not a normal analyzer + new /obj/item/healthanalyzer/simple(src) // Buffs wound treatment and gives details of wounds it scans + // "Ghetto" tools, things you shouldn't ideally use but you might have to + new /obj/item/stack/medical/bone_gel(src) // Ghetto T2/3 option for securing internals + new /obj/item/plunger/reinforced(src) // Can be used to mold heated metal at T3 blunt + +/obj/item/storage/backpack/duffelbag/synth_treatment_kit/trauma/advanced/unzipped + zipped_up = FALSE diff --git a/modular_skyrat/modules/medical/code/sprays.dm b/modular_skyrat/modules/medical/code/sprays.dm new file mode 100644 index 00000000000..8e2371e3f28 --- /dev/null +++ b/modular_skyrat/modules/medical/code/sprays.dm @@ -0,0 +1,12 @@ +/obj/item/reagent_containers/spray/hercuri/chilled + name = "chilled hercuri spray" // effective at cooling low-temperature burns but also is more efficienct at cooling high-temperature + desc = "A medical spray bottle. This one contains hercuri, a medicine used to negate the effects of dangerous high-temperature environments. \ + This one comes pre-chilled, making it especially good at cooling synthetic burns! \n\ + It has a bold warning label near the nozzle: ONLY USE IN EMERGENCIES! WILL CAUSE FREEZING! SECONDARY EFFECT ONLY USEFUL ON LIVING SYNTHS! INEFFECTIVE ON DECEASED! \n\ + There's a smaller warning label on the body of the spray: IN EVENT OF RUNAWAY ENDOTHERMY, APPLY SYSTEM CLEANER!" + var/starting_temperature = 100 + +/obj/item/reagent_containers/spray/hercuri/chilled/add_initial_reagents() + . = ..() + + reagents.chem_temp = starting_temperature diff --git a/modular_skyrat/modules/medical/code/wounds/muscle.dm b/modular_skyrat/modules/medical/code/wounds/muscle.dm index c1f9c3fc4c3..35cc54c7099 100644 --- a/modular_skyrat/modules/medical/code/wounds/muscle.dm +++ b/modular_skyrat/modules/medical/code/wounds/muscle.dm @@ -33,13 +33,14 @@ Overwriting of base procs */ /datum/wound/muscle/wound_injury(datum/wound/old_wound = null, attack_direction) - if(limb.held_index && victim.get_item_for_held_index(limb.held_index) && (disabling || prob(30 * severity))) - var/obj/item/I = victim.get_item_for_held_index(limb.held_index) - if(istype(I, /obj/item/offhand)) - I = victim.get_inactive_held_item() + var/obj/item/held_item = victim.get_item_for_held_index(limb.held_index || 0) + if(held_item && (disabling || prob(30 * severity))) + if(istype(held_item, /obj/item/offhand)) + held_item = victim.get_inactive_held_item() - if(I && victim.dropItemToGround(I)) - victim.visible_message(span_danger("[victim] drops [I] in shock!"), span_warning("The force on your [parse_zone(limb.body_zone)] causes you to drop [I]!"), vision_distance=COMBAT_MESSAGE_RANGE) + if(held_item && victim.dropItemToGround(held_item)) + victim.visible_message(span_danger("[victim] drops [held_item] in shock!"), \ + span_warning("The force on your [parse_zone(limb.body_zone)] causes you to drop [held_item]!"), vision_distance=COMBAT_MESSAGE_RANGE) return ..() @@ -173,3 +174,9 @@ id = "torn muscle" /datum/status_effect/wound/muscle/severe id = "ruptured tendon" + +/datum/status_effect/wound/muscle/robotic/moderate + id = "worn servo" + +/datum/status_effect/wound/muscle/robotic/severe + id = "severed hydraulic" diff --git a/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt.dm b/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt.dm new file mode 100644 index 00000000000..52995365b01 --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt.dm @@ -0,0 +1,398 @@ +/// The multiplier put against our movement effects if our victim has the determined reagent +#define ROBOTIC_WOUND_DETERMINATION_MOVEMENT_EFFECT_MOD 0.7 +/// The multiplier of stagger intensity on hit if our victim has the determined reagent +#define ROBOTIC_WOUND_DETERMINATION_STAGGER_MOVEMENT_MULT 0.7 + +/// The multiplier put against our movement effects if our limb is grasped +#define ROBOTIC_BLUNT_GRASPED_MOVEMENT_MULT 0.7 + +/datum/wound/blunt/robotic + name = "Robotic Blunt (Screws and bolts) Wound" + wound_flags = (ACCEPTS_GAUZE|SPLINT_OVERLAY|CAN_BE_GRASPED) + + default_scar_file = METAL_SCAR_FILE + + /// If we suffer severe head booboos, we can get brain traumas tied to them + var/datum/brain_trauma/active_trauma + /// What brain trauma group, if any, we can draw from for head wounds + var/brain_trauma_group + /// If we deal brain traumas, when is the next one due? + var/next_trauma_cycle + /// How long do we wait +/- 20% for the next trauma? + var/trauma_cycle_cooldown + + /// The ratio stagger score will be multiplied against for determining the final chance of moving away from the attacker. + var/stagger_movement_chance_ratio = 1 + /// The ratio stagger score will be multiplied against for determining the amount of pixelshifting we will do when we are hit. + var/stagger_shake_shift_ratio = 0.05 + + /// The ratio of stagger score to shake duration during a stagger() call + var/stagger_score_to_shake_duration_ratio = 0.1 + + /// In the stagger aftershock, the stagger score will be multiplied against for determining the chance of dropping held items. + var/stagger_drop_chance_ratio = 1.25 + /// In the stagger aftershock, the stagger score will be multiplied against for determining the chance of falling over. + var/stagger_fall_chance_ratio = 1 + + /// In the stagger aftershock, the stagger score will be multiplied against for determining how long we are knocked down for. + var/stagger_aftershock_knockdown_ratio = 0.5 + /// In the stagger after shock, the stagger score will be multiplied against this (if caused by movement) for determining how long we are knocked down for. + var/stagger_aftershock_knockdown_movement_ratio = 0.1 + + /// If the victim stops moving before the aftershock, aftershock effects will be multiplied against this. + var/aftershock_stopped_moving_score_mult = 0.1 + + /// The ratio damage applied will be multiplied against for determining our stagger score. + var/chest_attacked_stagger_mult = 2.5 + /// The minimum score an attack must do to trigger a stagger. + var/chest_attacked_stagger_minimum_score = 5 + /// The ratio of damage to stagger chance on hit. + var/chest_attacked_stagger_chance_ratio = 2 + + /// The base score given to stagger() when we successfully stagger on a move. + var/base_movement_stagger_score = 30 + /// The base chance of moving to trigger stagger(). + var/chest_movement_stagger_chance = 1 + + /// The base duration of a stagger()'s sprite shaking. + var/base_stagger_shake_duration = 1.5 SECONDS + /// The base duration of a stagger()'s sprite shaking if caused by movement. + var/base_stagger_movement_shake_duration = 1.5 SECONDS + + /// The ratio of stagger score to camera shake chance. + var/stagger_camera_shake_chance_ratio = 0.75 + /// The base duration of a stagger's aftershock's camerashake. + var/base_aftershock_camera_shake_duration = 1.5 SECONDS + /// The base strength of a stagger's aftershock's camerashake. + var/base_aftershock_camera_shake_strength = 0.5 + + /// The amount of x and y pixels we will be shaken around by during a movement stagger. + var/movement_stagger_shift = 1 + + /// If we are currently oscillating. If true, we cannot stagger(). + var/oscillating = FALSE + + /// % chance for hitting our limb to fix something. + var/percussive_maintenance_repair_chance = 10 + /// Damage must be under this to proc percussive maintenance. + var/percussive_maintenance_damage_max = 7 + /// Damage must be over this to proc percussive maintenance. + var/percussive_maintenance_damage_min = 0 + + /// The time, in world time, that we will be allowed to do another movement shake. Useful because it lets us prioritize attacked shakes over movement shakes. + var/time_til_next_movement_shake_allowed = 0 + + /// The percent our limb must get to max possible damage by burn damage alone to count as malleable if it has no T2 burn wound. + var/limb_burn_percent_to_max_threshold_for_malleable = 0.8 // must be 75% to max damage by burn damage alone + + /// The last time our victim has moved. Used for determining if we should increase or decrease the chance of having stagger aftershock. + var/last_time_victim_moved = 0 + + processes = TRUE + +/datum/wound_pregen_data/blunt_metal + abstract = TRUE + required_limb_biostate = BIO_METAL + wound_series = WOUND_SERIES_METAL_BLUNT_BASIC + required_wounding_types = list(WOUND_BLUNT) + +/datum/wound_pregen_data/blunt_metal/generate_scar_priorities() + return list("[BIO_METAL]") + +/datum/wound/blunt/robotic/set_victim(new_victim) + if(victim) + UnregisterSignal(victim, COMSIG_MOVABLE_MOVED) + UnregisterSignal(victim, COMSIG_MOB_AFTER_APPLY_DAMAGE) + if(new_victim) + RegisterSignal(new_victim, COMSIG_MOVABLE_MOVED, PROC_REF(victim_moved)) + RegisterSignal(new_victim, COMSIG_MOB_AFTER_APPLY_DAMAGE, PROC_REF(victim_attacked)) + + return ..() + +/datum/wound/blunt/robotic/get_limb_examine_description() + return span_warning("This limb looks loosely held together.") + +// this wound is unaffected by cryoxadone and pyroxadone +/datum/wound/blunt/robotic/on_xadone(power) + return + +/datum/wound/blunt/robotic/wound_injury(datum/wound/old_wound, attack_direction) + . = ..() + + // hook into gaining/losing gauze so crit bone wounds can re-enable/disable depending if they're slung or not + if(limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group) + processes = TRUE + active_trauma = victim.gain_trauma_type(brain_trauma_group, TRAUMA_RESILIENCE_WOUND) + next_trauma_cycle = world.time + (rand(100-WOUND_BONE_HEAD_TIME_VARIANCE, 100+WOUND_BONE_HEAD_TIME_VARIANCE) * 0.01 * trauma_cycle_cooldown) + + var/obj/item/held_item = victim.get_item_for_held_index(limb.held_index || 0) + if(held_item && (disabling || prob(30 * severity))) + if(istype(held_item, /obj/item/offhand)) + held_item = victim.get_inactive_held_item() + if(held_item && victim.dropItemToGround(held_item)) + victim.visible_message(span_danger("[victim] drops [held_item] in shock!"), span_warning("The force on your [limb.plaintext_zone] causes you to drop [held_item]!"), vision_distance=COMBAT_MESSAGE_RANGE) + +/datum/wound/blunt/robotic/remove_wound(ignore_limb, replaced) + . = ..() + + QDEL_NULL(active_trauma) + +/datum/wound/blunt/robotic/handle_process(seconds_per_tick, times_fired) + . = ..() + + if (!victim || IS_IN_STASIS(victim)) + return + + if (limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group && world.time > next_trauma_cycle) + if (active_trauma) + QDEL_NULL(active_trauma) + else + active_trauma = victim.gain_trauma_type(brain_trauma_group, TRAUMA_RESILIENCE_WOUND) + next_trauma_cycle = world.time + (rand(100-WOUND_BONE_HEAD_TIME_VARIANCE, 100+WOUND_BONE_HEAD_TIME_VARIANCE) * 0.01 * trauma_cycle_cooldown) + +/// If true, allows our superstructure to be modified if we are T3. RCDs can always fix our superstructure. +/datum/wound/blunt/robotic/proc/limb_malleable() + if (!isnull(get_overheat_wound())) + return TRUE + var/burn_damage_to_max = (limb.burn_dam / limb.max_damage) // only exists for the weird case where it cant get a overheat wound + if (burn_damage_to_max >= limb_burn_percent_to_max_threshold_for_malleable) + return TRUE + return FALSE + +/// If we have one, returns a robotic overheat wound of severe severity or higher. Null otherwise. +/datum/wound/blunt/robotic/proc/get_overheat_wound() + RETURN_TYPE(/datum/wound/burn/robotic/overheat) + for (var/datum/wound/found_wound as anything in limb.wounds) + var/datum/wound_pregen_data/pregen_data = found_wound.get_pregen_data() + if (pregen_data.wound_series == WOUND_SERIES_METAL_BURN_OVERHEAT && found_wound.severity >= WOUND_SEVERITY_SEVERE) // meh solution but whateva + return found_wound + return null + +/// If our victim is lying down and is attacked in the chest, effective oscillation damage is multiplied against this. +#define OSCILLATION_ATTACKED_LYING_DOWN_EFFECT_MULT 0.5 + +/// If the attacker is wearing a diag hud, chance of percussive maintenance succeeding is multiplied against this. +#define PERCUSSIVE_MAINTENANCE_DIAG_HUD_CHANCE_MULT 1.5 +/// If our wound has been scanned by a wound analyzer, chance of percussive maintenance succeeding is multiplied against this. +#define PERCUSSIVE_MAINTENANCE_WOUND_SCANNED_CHANCE_MULT 1.5 +/// If the attacker is NOT our victim, chance of percussive maintenance succeeding is multiplied against this. +#define PERCUSSIVE_MAINTENANCE_ATTACKER_NOT_VICTIM_CHANCE_MULT 2.5 + +/// Signal handler proc to when our victim has damage applied via apply_damage(), which is a external attack. +/datum/wound/blunt/robotic/proc/victim_attacked(datum/source, damage, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item) + SIGNAL_HANDLER + + if (def_zone != limb.body_zone) // use this proc since receive damage can also be called for like, chems and shit + return + if(!victim) + return + + var/effective_damage = (damage - blocked) + + var/obj/item/stack/gauze = limb.current_gauze + if(gauze) + effective_damage *= gauze.splint_factor + + switch (limb.body_zone) + + if(BODY_ZONE_CHEST) + var/oscillation_mult = 1 + if (victim.body_position == LYING_DOWN) + oscillation_mult *= OSCILLATION_ATTACKED_LYING_DOWN_EFFECT_MULT + var/oscillation_damage = effective_damage + var/stagger_damage = oscillation_damage * chest_attacked_stagger_mult + if (victim.has_status_effect(/datum/status_effect/determined)) + oscillation_damage *= ROBOTIC_WOUND_DETERMINATION_STAGGER_MOVEMENT_MULT + if ((stagger_damage >= chest_attacked_stagger_minimum_score) && prob(oscillation_damage * chest_attacked_stagger_chance_ratio)) + stagger(stagger_damage * oscillation_mult, attack_direction, attacking_item, shift = stagger_damage * stagger_shake_shift_ratio) + + if(!uses_percussive_maintenance() || damage < percussive_maintenance_damage_min || damage > percussive_maintenance_damage_max || damagetype != BRUTE || sharpness) + return + var/success_chance_mult = 1 + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + success_chance_mult *= PERCUSSIVE_MAINTENANCE_WOUND_SCANNED_CHANCE_MULT + var/mob/living/user + if (isatom(attacking_item)) + var/atom/attacking_atom = attacking_item + user = attacking_atom.loc // nullable + + if (istype(user)) + if (HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD)) + success_chance_mult *= PERCUSSIVE_MAINTENANCE_DIAG_HUD_CHANCE_MULT + + if (user != victim) + success_chance_mult *= PERCUSSIVE_MAINTENANCE_ATTACKER_NOT_VICTIM_CHANCE_MULT // encourages people to get other people to beat the shit out of their limbs + if (prob(percussive_maintenance_repair_chance * success_chance_mult)) + handle_percussive_maintenance_success(attacking_item, user) + else + handle_percussive_maintenance_failure(attacking_item, user) + +#undef OSCILLATION_ATTACKED_LYING_DOWN_EFFECT_MULT +#undef PERCUSSIVE_MAINTENANCE_DIAG_HUD_CHANCE_MULT +#undef PERCUSSIVE_MAINTENANCE_WOUND_SCANNED_CHANCE_MULT +#undef PERCUSSIVE_MAINTENANCE_ATTACKER_NOT_VICTIM_CHANCE_MULT + +/// The percent, in decimal, of a stagger's shake() duration, that will be used in a addtimer() to queue aftershock(). +#define STAGGER_PERCENT_OF_SHAKE_DURATION_TO_AFTERSHOCK_DELAY 0.65 // 1 = happens at the end, .5 = happens halfway through + +/// Causes an oscillation, which 1. has a chance to move our victim away from the attacker, and 2. after a delay, calls aftershock(). +/datum/wound/blunt/robotic/proc/stagger(stagger_score, attack_direction, obj/item/attacking_item, from_movement, shake_duration = base_stagger_shake_duration, shift, knockdown_ratio = stagger_aftershock_knockdown_ratio) + if (oscillating) + return + + var/self_message = "Your [limb.plaintext_zone] oscillates" + var/message = "[victim]'s [limb.plaintext_zone] oscillates" + if (attacking_item) + message += " from the impact" + else if (from_movement) + message += " from the movement" + message += "!" + self_message += "! You might be able to avoid an aftershock by stopping and waiting..." + + if (isnull(attack_direction)) + attack_direction = get_dir(victim, attacking_item) + + if (!isnull(attack_direction) && prob(stagger_score * stagger_movement_chance_ratio)) + to_chat(victim, span_warning("The force of the blow sends you reeling!")) + var/turf/target_loc = get_step(victim, attack_direction) + victim.Move(target_loc) + + victim.visible_message(span_warning(message), ignored_mobs = victim) + to_chat(victim, span_warning(self_message)) + victim.balloon_alert(victim, "oscillation! stop moving") + + victim.Shake(pixelshiftx = shift, pixelshifty = shift, duration = shake_duration) + var/aftershock_delay = (shake_duration * STAGGER_PERCENT_OF_SHAKE_DURATION_TO_AFTERSHOCK_DELAY) + var/knockdown_time = stagger_score * knockdown_ratio + addtimer(CALLBACK(src, PROC_REF(aftershock), stagger_score, attack_direction, attacking_item, world.time, knockdown_time), aftershock_delay) + oscillating = TRUE + +#undef STAGGER_PERCENT_OF_SHAKE_DURATION_TO_AFTERSHOCK_DELAY + +#define AFTERSHOCK_GRACE_THRESHOLD_PERCENT 0.33 // lower mult = later grace period = more forgiving + +/** + * Timer proc from stagger(). + * + * Based on chance, causes items to be dropped, knockdown to be applied, and/or screenshake to occur. + * Chance is massively reduced if the victim isn't moving. + */ +/datum/wound/blunt/robotic/proc/aftershock(stagger_score, attack_direction, obj/item/attacking_item, stagger_starting_time, knockdown_time) + if (!still_exists()) + return FALSE + + var/message = "The oscillations from your [limb.plaintext_zone] spread, " + var/limb_message = "causing " + var/limb_affected + + var/stopped_moving_grace_threshold = (world.time - ((world.time - stagger_starting_time) * AFTERSHOCK_GRACE_THRESHOLD_PERCENT)) + var/victim_stopped_moving = (last_time_victim_moved <= stopped_moving_grace_threshold) + if (victim_stopped_moving) + stagger_score *= aftershock_stopped_moving_score_mult + + if (prob(stagger_score * stagger_drop_chance_ratio)) + limb_message += "your hands" + victim.drop_all_held_items() + limb_affected = TRUE + + if (prob(stagger_score * stagger_fall_chance_ratio)) + if (limb_affected) + limb_message += " and " + limb_message += "your legs" + victim.Knockdown(knockdown_time) + limb_affected = TRUE + + if (prob(stagger_score * stagger_camera_shake_chance_ratio)) + if (limb_affected) + limb_message += " and " + limb_message += "your head" + shake_camera(victim, base_aftershock_camera_shake_duration, base_aftershock_camera_shake_strength) + limb_affected = TRUE + + if (limb_affected) + message += "[limb_message] to shake uncontrollably!" + else + message += "but pass harmlessly" + if (victim_stopped_moving) + message += " thanks to your stillness" + message += "." + + to_chat(victim, span_danger(message)) + victim.balloon_alert(victim, "oscillation over") + + oscillating = FALSE + +#undef AFTERSHOCK_GRACE_THRESHOLD_PERCENT + +/// Called when percussive maintenance succeeds at its random roll. +/datum/wound/blunt/robotic/proc/handle_percussive_maintenance_success(attacking_item, mob/living/user) + victim.visible_message(span_green("[victim]'s [limb.plaintext_zone] rattles from the impact, but looks a lot more secure!"), \ + span_green("Your [limb.plaintext_zone] rattles into place!")) + remove_wound() + +/// Called when percussive maintenance fails at its random roll. +/datum/wound/blunt/robotic/proc/handle_percussive_maintenance_failure(attacking_item, mob/living/user) + to_chat(victim, span_warning("Your [limb.plaintext_zone] rattles around, but you don't sense any sign of improvement.")) + +/// If our victim has no gravity, the effects of movement are multiplied by this. +#define VICTIM_MOVED_NO_GRAVITY_EFFECT_MULT 0.5 +/// If our victim is resting, or is walking and isnt forced to move, the effects of movement are multiplied by this. +#define VICTIM_MOVED_CAREFULLY_EFFECT_MULT 0.25 + +/// Signal handler proc that applies movements affect to our victim if they were moved. +/datum/wound/blunt/robotic/proc/victim_moved(datum/source, atom/old_loc, dir, forced, list/old_locs) + SIGNAL_HANDLER + + var/overall_mult = 1 + + var/obj/item/stack/gauze = limb.current_gauze + if (gauze) + overall_mult *= gauze.splint_factor + if (!victim.has_gravity(get_turf(victim))) + overall_mult *= VICTIM_MOVED_NO_GRAVITY_EFFECT_MULT + else if (victim.body_position == LYING_DOWN || (!forced && victim.move_intent == MOVE_INTENT_WALK)) + overall_mult *= VICTIM_MOVED_CAREFULLY_EFFECT_MULT + if (victim.has_status_effect(/datum/status_effect/determined)) + overall_mult *= ROBOTIC_WOUND_DETERMINATION_MOVEMENT_EFFECT_MOD + if (limb.grasped_by) + overall_mult *= ROBOTIC_BLUNT_GRASPED_MOVEMENT_MULT + + overall_mult *= get_buckled_movement_consequence_mult(victim.buckled) + + if (limb.body_zone == BODY_ZONE_CHEST) + var/stagger_chance = chest_movement_stagger_chance * overall_mult + if (prob(stagger_chance)) + stagger(base_movement_stagger_score, shake_duration = base_stagger_movement_shake_duration, from_movement = TRUE, shift = movement_stagger_shift, knockdown_ratio = stagger_aftershock_knockdown_movement_ratio) + + last_time_victim_moved = world.time + +#undef VICTIM_MOVED_NO_GRAVITY_EFFECT_MULT +#undef VICTIM_MOVED_CAREFULLY_EFFECT_MULT + +/// If our victim is buckled to a generic object, movement effects will be multiplied against this. +#define VICTIM_BUCKLED_BASE_MOVEMENT_EFFECT_MULT 0.5 +/// If our victim is buckled to a medical bed (e.g. rollerbed), movement effects will be multiplied against this. +#define VICTIM_BUCKLED_ROLLER_BED_MOVEMENT_EFFECT_MULT 0.05 + +/// Returns a multiplier to our movement effects based on what our victim is buckled to. +/datum/wound/blunt/robotic/proc/get_buckled_movement_consequence_mult(atom/movable/buckled_to) + if (!buckled_to) + return 1 + + if (istype(buckled_to, /obj/structure/bed/medical)) + return VICTIM_BUCKLED_ROLLER_BED_MOVEMENT_EFFECT_MULT + else + return VICTIM_BUCKLED_BASE_MOVEMENT_EFFECT_MULT + +#undef VICTIM_BUCKLED_BASE_MOVEMENT_EFFECT_MULT +#undef VICTIM_BUCKLED_ROLLER_BED_MOVEMENT_EFFECT_MULT + +/// If this wound can be treated in its current state by just hitting it with a low force object. Exists for conditional logic, e.g. "Should we respond +/// to percussive maintenance right now?". Critical blunt uses this to only react when the limb is malleable and superstructure is broken. +/datum/wound/blunt/robotic/proc/uses_percussive_maintenance() + return FALSE + +#undef ROBOTIC_WOUND_DETERMINATION_MOVEMENT_EFFECT_MOD +#undef ROBOTIC_WOUND_DETERMINATION_STAGGER_MOVEMENT_MULT + +#undef ROBOTIC_BLUNT_GRASPED_MOVEMENT_MULT diff --git a/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T1.dm b/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T1.dm new file mode 100644 index 00000000000..9cd2ae0adbb --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T1.dm @@ -0,0 +1,65 @@ +/datum/wound/blunt/robotic/moderate + name = "Loosened Screws" + desc = "Various semi-external fastening instruments have loosened, causing components to jostle, inhibiting limb control." + treat_text = "Recommend topical re-fastening of instruments with a screwdriver, though percussive maintenance via low-force bludgeoning may suffice - \ + albeit at risk of worsening the injury." + examine_desc = "appears to be loosely secured" + occur_text = "jostles awkwardly and seems to slightly unfasten" + severity = WOUND_SEVERITY_MODERATE + simple_treat_text = "Bandaging the wound will reduce the impact until it's screws are secured - which is faster if done by \ + someone else, a roboticist, an engineer, or with a diagnostic HUD." + homemade_treat_text = "In a pinch, percussive maintenance can reset the screws - the chance of which is increased if done by someone else or \ + with a diagnostic HUD!" + status_effect_type = /datum/status_effect/wound/blunt/robotic/moderate + treatable_tools = list(TOOL_SCREWDRIVER) + interaction_efficiency_penalty = 1.2 + limp_slowdown = 2.5 + limp_chance = 30 + threshold_penalty = 20 + can_scar = FALSE + a_or_from = "from" + +/datum/wound_pregen_data/blunt_metal/loose_screws + abstract = FALSE + wound_path_to_generate = /datum/wound/blunt/robotic/moderate + viable_zones = list(BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + threshold_minimum = 30 + +/datum/wound/blunt/robotic/moderate/uses_percussive_maintenance() + return TRUE + +/datum/wound/blunt/robotic/moderate/treat(obj/item/potential_treater, mob/user) + if (potential_treater.tool_behaviour == TOOL_SCREWDRIVER) + fasten_screws(potential_treater, user) + return TRUE + + return ..() + +/// The main treatment for T1 blunt. Uses a screwdriver, guaranteed to always work, better with a diag hud. Removes the wound. +/datum/wound/blunt/robotic/moderate/proc/fasten_screws(obj/item/screwdriver_tool, mob/user) + if (!screwdriver_tool.tool_start_check()) + return + + var/delay_mult = 1 + + if (user == victim) + delay_mult *= 3 + + if (HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD)) + delay_mult *= 0.5 + + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + delay_mult *= 0.5 + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + victim.visible_message(span_notice("[user] begins fastening the screws of [their_or_other] [limb.plaintext_zone]..."), \ + span_notice("You begin fastening the screws of [your_or_other] [limb.plaintext_zone]...")) + + if (!screwdriver_tool.use_tool(target = victim, user = user, delay = (10 SECONDS * delay_mult), volume = 50, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return + + victim.visible_message(span_green("[user] finishes fastening [their_or_other] [limb.plaintext_zone]!"), \ + span_green("You finish fastening [your_or_other] [limb.plaintext_zone]!")) + + remove_wound() diff --git a/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T2.dm b/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T2.dm new file mode 100644 index 00000000000..68bac1fe12b --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T2.dm @@ -0,0 +1,52 @@ +/datum/wound/blunt/robotic/secures_internals/severe + name = "Detached Fastenings" + desc = "Various fastening devices are extremely loose and solder has disconnected at multiple points, causing significant jostling of internal components and \ + noticable limb dysfunction." + treat_text = "Fastening of bolts and screws by a qualified technician (though bone gel may suffice in the absence of one) followed by re-soldering." + examine_desc = "jostles with every move, solder visibly broken" + occur_text = "visibly cracks open, solder flying everywhere" + severity = WOUND_SEVERITY_SEVERE + + simple_treat_text = "If on the chest, walk, grasp it, splint, rest or buckle yourself to something to reduce movement effects. \ + Afterwards, get someone else, ideally a robo/engi to screwdriver/wrench it, and then re-solder it!" + homemade_treat_text = "If unable to screw/wrench, bone gel can, over time, secure inner components at risk of corrossion. \ + Alternatively, crowbar the limb open to expose the internals - this will make it easier to re-secure them, but has a high risk of shocking you, \ + so use insulated gloves. This will cripple the limb, so use it only as a last resort!" + + wound_flags = (ACCEPTS_GAUZE|MANGLES_EXTERIOR|SPLINT_OVERLAY|CAN_BE_GRASPED) + treatable_by = list(/obj/item/stack/medical/bone_gel) + status_effect_type = /datum/status_effect/wound/blunt/robotic/severe + treatable_tools = list(TOOL_WELDER, TOOL_CROWBAR) + + interaction_efficiency_penalty = 2 + limp_slowdown = 6 + limp_chance = 60 + + brain_trauma_group = BRAIN_TRAUMA_MILD + trauma_cycle_cooldown = 1.5 MINUTES + + threshold_penalty = 40 + + base_movement_stagger_score = 40 + + chest_attacked_stagger_chance_ratio = 5 + chest_attacked_stagger_mult = 3 + + chest_movement_stagger_chance = 3 + + stagger_aftershock_knockdown_ratio = 0.3 + stagger_aftershock_knockdown_movement_ratio = 0.2 + + a_or_from = "from" + + ready_to_secure_internals = TRUE + ready_to_resolder = FALSE + + scar_keyword = "bluntsevere" + +/datum/wound_pregen_data/blunt_metal/fastenings + abstract = FALSE + + wound_path_to_generate = /datum/wound/blunt/robotic/secures_internals/severe + + threshold_minimum = 65 diff --git a/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T3.dm b/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T3.dm new file mode 100644 index 00000000000..aa85498108a --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/synth/blunt/robotic_blunt_T3.dm @@ -0,0 +1,385 @@ +/datum/wound/blunt/robotic/secures_internals/critical + name = "Collapsed Superstructure" + desc = "The superstructure has totally collapsed in one or more locations, causing extreme internal oscillation with every move and massive limb dysfunction" + treat_text = "Reforming of superstructure via either RCD or manual molding, followed by typical treatment of loosened internals. \ + To manually mold, the limb must be aggressively grabbed and welded held to it to make it malleable (though attacking it til thermal overload may be adequate) \ + followed by firmly grasping and molding the limb with heat-resistant gloves." + occur_text = "caves in on itself, damaged solder and shrapnel flying out in a miniature explosion" + examine_desc = "has caved in, with internal components visible through gaps in the metal" + severity = WOUND_SEVERITY_CRITICAL + + disabling = TRUE + + simple_treat_text = "If on the chest, walk, grasp it, splint, rest or buckle yourself to something to reduce movement effects. \ + Afterwards, get someone, ideally a robo/engi to firmly grasp the limb and hold a welder to it. Then, have them use their hands to mold the metal - \ + careful though, it's hot! An RCD can skip all this, but is hard to come by. Afterwards, have them screw/wrench and then re-solder the limb!" + + homemade_treat_text = "The metal can be made malleable by repeated application of a welder, to a severe burn. Afterwards, a plunger can reset the metal, \ + as can percussive maintenance. After the metal is reset, if unable to screw/wrench, bone gel can, over time, secure inner components at risk of corrossion. \ + Alternatively, crowbar the limb open to expose the internals - this will make it easier to re-secure them, but has a high risk of shocking you, \ + so use insulated gloves. This will cripple the limb, so use it only as a last resort!" + + interaction_efficiency_penalty = 2.8 + limp_slowdown = 8 + limp_chance = 80 + threshold_penalty = 60 + + brain_trauma_group = BRAIN_TRAUMA_SEVERE + trauma_cycle_cooldown = 2.5 MINUTES + + scar_keyword = "bluntcritical" + + status_effect_type = /datum/status_effect/wound/blunt/robotic/critical + + sound_effect = 'sound/effects/wounds/crack2.ogg' + + wound_flags = (ACCEPTS_GAUZE|MANGLES_EXTERIOR|SPLINT_OVERLAY|CAN_BE_GRASPED) + treatable_by = list(/obj/item/stack/medical/bone_gel) + status_effect_type = /datum/status_effect/wound/blunt/robotic/critical + treatable_tools = list(TOOL_WELDER, TOOL_CROWBAR) + + base_movement_stagger_score = 55 + + base_aftershock_camera_shake_duration = 1.75 SECONDS + base_aftershock_camera_shake_strength = 1 + + chest_attacked_stagger_chance_ratio = 6.5 + chest_attacked_stagger_mult = 4 + + chest_movement_stagger_chance = 14 + + aftershock_stopped_moving_score_mult = 0.3 + + stagger_aftershock_knockdown_ratio = 0.5 + stagger_aftershock_knockdown_movement_ratio = 0.3 + + percussive_maintenance_repair_chance = 3 + percussive_maintenance_damage_max = 6 + + regen_time_needed = 60 SECONDS + gel_damage = 20 + + ready_to_secure_internals = FALSE + ready_to_resolder = FALSE + + a_or_from = "a" + + /// Has the first stage of our treatment been completed? E.g. RCDed, manually molded... + var/superstructure_remedied = FALSE + +/datum/wound_pregen_data/blunt_metal/superstructure + abstract = FALSE + wound_path_to_generate = /datum/wound/blunt/robotic/secures_internals/critical + threshold_minimum = 125 + +/datum/wound/blunt/robotic/secures_internals/critical/item_can_treat(obj/item/potential_treater) + if(!superstructure_remedied) + if(istype(potential_treater, /obj/item/construction/rcd)) + return TRUE + if(limb_malleable() && istype(potential_treater, /obj/item/plunger)) + return TRUE + return ..() + +/datum/wound/blunt/robotic/secures_internals/critical/check_grab_treatments(obj/item/potential_treater, mob/user) + if(potential_treater.tool_behaviour == TOOL_WELDER && (!superstructure_remedied && !limb_malleable())) + return TRUE + return ..() + +/datum/wound/blunt/robotic/secures_internals/critical/treat(obj/item/item, mob/user) + if(!superstructure_remedied) + if(istype(item, /obj/item/construction/rcd)) + return rcd_superstructure(item, user) + if(uses_percussive_maintenance() && istype(item, /obj/item/plunger)) + return plunge(item, user) + if(item.tool_behaviour == TOOL_WELDER && !limb_malleable() && isliving(victim.pulledby)) + var/mob/living/living_puller = victim.pulledby + if (living_puller.grab_state >= GRAB_AGGRESSIVE) // only let other people do this + return heat_metal(item, user) + return ..() + +/datum/wound/blunt/robotic/secures_internals/critical/try_handling(mob/living/carbon/human/user) + if(user.pulling != victim || user.zone_selected != limb.body_zone) + return FALSE + + if(superstructure_remedied || !limb_malleable()) + return FALSE + + if(user.grab_state < GRAB_AGGRESSIVE) + to_chat(user, span_warning("You must have [victim] in an aggressive grab to manipulate [victim.p_their()] [lowertext(name)]!")) + return TRUE + + user.visible_message(span_danger("[user] begins softly pressing against [victim]'s collapsed [limb.plaintext_zone]..."), \ + span_notice("You begin softly pressing against [victim]'s collapsed [limb.plaintext_zone]..."), \ + ignored_mobs = victim) + to_chat(victim, span_userdanger("[user] begins pressing against your collapsed [limb.plaintext_zone]!")) + + var/delay_mult = 1 + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + delay_mult *= 0.75 + + if(!do_after(user, 8 SECONDS, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return + mold_metal(user) + return TRUE + +/// If the user turns combat mode on after they start to mold metal, our limb takes this much brute damage. +#define MOLD_METAL_SABOTAGE_BRUTE_DAMAGE 30 // really punishing +/// Our limb takes this much brute damage on a failed mold metal attempt. +#define MOLD_METAL_FAILURE_BRUTE_DAMAGE 5 +/// If the user's hand is unprotected from heat when they mold metal, we do this much burn damage to it. +#define MOLD_METAL_HAND_BURNT_BURN_DAMAGE 5 +/// Gloves must be above or at this threshold to cause the user to not be burnt apon trying to mold metal. +#define MOLD_METAL_HEAT_RESISTANCE_THRESHOLD 1000 // less than the black gloves max resist +/** + * Standard treatment for 1st step of T3, after the limb has been made malleable. Done via aggrograb. + * High chance to work, very high with robo/engi wires and diag hud. + * Can be sabotaged by switching to combat mode. + * Deals brute to the limb on failure. + * Burns the hand of the user if its not insulated. + */ +/datum/wound/blunt/robotic/secures_internals/critical/proc/mold_metal(mob/living/carbon/human/user) + var/chance = 60 + + var/knows_wires = FALSE + if (HAS_TRAIT(user, TRAIT_KNOW_ROBO_WIRES)) + chance *= 2 + knows_wires = TRUE + else if (HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES)) + chance *= 1.25 + knows_wires = TRUE + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + chance *= 2 + if (HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD)) + if (knows_wires) + chance *= 1.25 + else + chance *= 2 + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + + if ((user != victim && user.combat_mode)) + user.visible_message(span_bolddanger("[user] molds [their_or_other] [limb.plaintext_zone] into a really silly shape! What a goofball!"), \ + span_danger("You maliciously mold [victim]'s [limb.plaintext_zone] into a weird shape, damaging it in the process!"), ignored_mobs = victim) + to_chat(victim, span_userdanger("[user] molds your [limb.plaintext_zone] into a weird shape, damaging it in the process!")) + + limb.receive_damage(brute = MOLD_METAL_SABOTAGE_BRUTE_DAMAGE, wound_bonus = CANT_WOUND, damage_source = user) + else if (prob(chance)) + user.visible_message(span_green("[user] carefully molds [their_or_other] [limb.plaintext_zone] into the proper shape!"), \ + span_green("You carefully mold [victim]'s [limb.plaintext_zone] into the proper shape!"), ignored_mobs = victim) + to_chat(victim, span_green("[user] carefully molds your [limb.plaintext_zone] into the proper shape!")) + to_chat(user, span_green("[capitalize(your_or_other)] [limb.plaintext_zone] has been molded into the proper shape! Your next step is to use a screwdriver/wrench to secure your internals.")) + set_superstructure_status(TRUE) + else + user.visible_message(span_danger("[user] accidentally molds [their_or_other] [limb.plaintext_zone] into the wrong shape!"), \ + span_danger("You accidentally mold [your_or_other] [limb.plaintext_zone] into the wrong shape!"), ignored_mobs = victim) + to_chat(victim, span_userdanger("[user] accidentally molds your [limb.plaintext_zone] into the wrong shape!")) + + limb.receive_damage(brute = MOLD_METAL_FAILURE_BRUTE_DAMAGE, damage_source = user, wound_bonus = CANT_WOUND) + + var/sufficiently_insulated_gloves = FALSE + var/obj/item/clothing/gloves/worn_gloves = user.gloves + if ((worn_gloves?.heat_protection & HANDS) && worn_gloves?.max_heat_protection_temperature && worn_gloves.max_heat_protection_temperature >= MOLD_METAL_HEAT_RESISTANCE_THRESHOLD) + sufficiently_insulated_gloves = TRUE + + if (sufficiently_insulated_gloves || HAS_TRAIT(user, TRAIT_RESISTHEAT) || HAS_TRAIT(user, TRAIT_RESISTHEATHANDS)) + return + + to_chat(user, span_danger("You burn your hand on [victim]'s [limb.plaintext_zone]!")) + var/obj/item/bodypart/affecting = user.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") + affecting?.receive_damage(burn = MOLD_METAL_HAND_BURNT_BURN_DAMAGE, damage_source = limb) + +#undef MOLD_METAL_SABOTAGE_BRUTE_DAMAGE +#undef MOLD_METAL_FAILURE_BRUTE_DAMAGE +#undef MOLD_METAL_HAND_BURNT_BURN_DAMAGE +#undef MOLD_METAL_HEAT_RESISTANCE_THRESHOLD + +/** + * A "safe" way to give our victim a T2 burn wound. Requires an aggrograb, and a welder. This is required to mold metal, the 1st step of treatment. + * Guaranteed to work. After a delay, causes a T2 burn wound with no damage. + * Can be sabotaged by enabling combat mode to cause a T3. + */ +/datum/wound/blunt/robotic/secures_internals/critical/proc/heat_metal(obj/item/welder, mob/living/user) + if (!welder.tool_use_check()) + return TRUE + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + + user?.visible_message(span_danger("[user] carefully holds [welder] to [their_or_other] [limb.plaintext_zone], slowly heating it..."), \ + span_warning("You carefully hold [welder] to [your_or_other] [limb.plaintext_zone], slowly heating it..."), ignored_mobs = victim) + + var/delay_mult = 1 + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + delay_mult *= 0.75 + + if (!welder.use_tool(target = victim, user = user, delay = 10 SECONDS * delay_mult, volume = 50, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + var/wound_path = /datum/wound/burn/robotic/overheat/severe + if (user != victim && user.combat_mode) + wound_path = /datum/wound/burn/robotic/overheat/critical // it really isnt that bad, overheat wounds are a bit funky + user.visible_message(span_danger("[user] heats [victim]'s [limb.plaintext_zone] aggressively, overheating it far beyond the necessary point!"), \ + span_danger("You heat [victim]'s [limb.plaintext_zone] aggressively, overheating it far beyond the necessary point!"), ignored_mobs = victim) + to_chat(victim, span_userdanger("[user] heats your [limb.plaintext_zone] aggressively, overheating it far beyond the necessary point!")) + + var/datum/wound/burn/robotic/overheat/overheat_wound = new wound_path + overheat_wound.apply_wound(limb, wound_source = welder) + + to_chat(user, span_green("[capitalize(your_or_other)] [limb.plaintext_zone] is now heated, allowing it to be molded! Your next step is to have someone physically reset the superstructure with their hands.")) + return TRUE + +/// Cost of an RCD to quickly fix our broken in raw matter +#define ROBOTIC_T3_BLUNT_WOUND_RCD_COST 25 +/// Cost of an RCD to quickly fix our broken in silo material +#define ROBOTIC_T3_BLUNT_WOUND_RCD_SILO_COST ROBOTIC_T3_BLUNT_WOUND_RCD_COST / 4 + +/// The "premium" treatment for 1st step of T3. Requires an RCD. Guaranteed to work, but can cause damage if delay is high. +/datum/wound/blunt/robotic/secures_internals/critical/proc/rcd_superstructure(obj/item/construction/rcd/treating_rcd, mob/user) + if (!treating_rcd.tool_use_check()) + return TRUE + + var/has_enough_matter = (treating_rcd.get_matter(user) > ROBOTIC_T3_BLUNT_WOUND_RCD_COST) + var/silo_has_enough_materials = (treating_rcd.get_silo_iron() > ROBOTIC_T3_BLUNT_WOUND_RCD_SILO_COST) + + if (!silo_has_enough_materials && has_enough_matter) + return TRUE + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + + var/base_time = 10 SECONDS + var/delay_mult = 1 + var/knows_wires = FALSE + if (victim == user) + delay_mult *= 3 // real slow + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + delay_mult *= 0.75 + if (HAS_TRAIT(user, TRAIT_KNOW_ROBO_WIRES)) + delay_mult *= 0.5 + knows_wires = TRUE + else if (HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES)) + delay_mult *= 0.5 // engis are accustomed to using RCDs + knows_wires = TRUE + if (HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD)) + if (knows_wires) + delay_mult *= 0.85 + else + delay_mult *= 0.5 + + var/final_time = (base_time * delay_mult) + var/misused = (final_time > base_time) // if we damage the limb when we're done + + if (user) + var/misused_text = (misused ? "unsteadily " : "") + + var/message = "[user]'s RCD whirs to life as it begins [misused_text]replacing the damaged superstructure of [their_or_other] [limb.plaintext_zone]..." + var/self_message = "Your RCD whirs to life as it begins [misused_text]replacing the damaged superstructure of [your_or_other] [limb.plaintext_zone]..." + + if (misused) // warning span if misused, notice span otherwise + message = span_danger(message) + self_message = span_danger(self_message) + else + message = span_notice(message) + self_message = span_notice(self_message) + + user.visible_message(message, self_message) + + if (!treating_rcd.use_tool(target = victim, user = user, delay = final_time, volume = 50, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + playsound(get_turf(treating_rcd), 'sound/machines/ping.ogg', 75) // celebration! we did it + set_superstructure_status(TRUE) + + var/use_amount = (silo_has_enough_materials ? ROBOTIC_T3_BLUNT_WOUND_RCD_SILO_COST : ROBOTIC_T3_BLUNT_WOUND_RCD_COST) + treating_rcd.useResource(use_amount, user) + + if (user) + var/misused_text = (misused ? ", though it replaced a bit more than it should've..." : "!") + var/message = "[user]'s RCD lets out a small ping as it finishes replacing the superstructure of [their_or_other] [limb.plaintext_zone][misused_text]" + var/self_message = "Your RCD lets out a small ping as it finishes replacing the superstructure of [your_or_other] [limb.plaintext_zone][misused_text]" + if (misused) + message = span_danger(message) + self_message = span_danger(self_message) + else + message = span_green(message) + self_message = span_green(self_message) + + user.visible_message(message, self_message) + if (misused) + limb.receive_damage(brute = 10, damage_source = treating_rcd, wound_bonus = CANT_WOUND) + // the double message is fine here, since the first message also tells you if you fucked up and did some damage + to_chat(user, span_green("The superstructure has been reformed! Your next step is to secure the internals via a screwdriver/wrench.")) + return TRUE + +#undef ROBOTIC_T3_BLUNT_WOUND_RCD_COST +#undef ROBOTIC_T3_BLUNT_WOUND_RCD_SILO_COST + +/** + * Goofy but practical, this is the superior ghetto self-tend of T3's first step compared to percussive maintenance. + * Still requires the limb to be malleable, but has a high chance of success and doesn't burn your hand, but gives worse bonuses for wires/HUD. + */ +/datum/wound/blunt/robotic/secures_internals/critical/proc/plunge(obj/item/plunger/treating_plunger, mob/user) + if (!treating_plunger.tool_use_check()) + return TRUE + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + user?.visible_message(span_notice("[user] begins plunging at the dents on [their_or_other] [limb.plaintext_zone] with [treating_plunger]..."), \ + span_green("You begin plunging at the dents on [your_or_other] [limb.plaintext_zone] with [treating_plunger]...")) + + var/delay_mult = 1 + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + delay_mult *= 0.75 + + delay_mult /= treating_plunger.plunge_mod + + if (!treating_plunger.use_tool(target = victim, user = user, delay = 8 SECONDS * delay_mult, volume = 50, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + var/success_chance = 80 + if (victim == user) + success_chance *= 0.6 + + if (HAS_TRAIT(user, TRAIT_KNOW_ROBO_WIRES)) + success_chance *= 1.25 + else if (HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES)) + success_chance *= 1.1 + if (HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD)) + success_chance *= 1.25 // its kinda alien to do this, so even people with the wires get the full bonus of diag huds + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + success_chance *= 1.5 + + if (prob(success_chance)) + user?.visible_message(span_green("[victim]'s [limb.plaintext_zone] lets out a sharp POP as [treating_plunger] forces it into its normal position!"), \ + span_green("[victim]'s [limb.plaintext_zone] lets out a sharp POP as your [treating_plunger] forces it into its normal position!")) + to_chat(user, span_green("[capitalize(your_or_other)] [limb.plaintext_zone]'s structure has been reset to it's proper position! Your next step is to secure it with a screwdriver/wrench, though bone gel would also work.")) + set_superstructure_status(TRUE) + else + user?.visible_message(span_danger("[victim]'s [limb.plaintext_zone] splinters from [treating_plunger]'s plunging!"), \ + span_danger("[capitalize(your_or_other)] [limb.plaintext_zone] splinters from your [treating_plunger]'s plunging!")) + limb.receive_damage(brute = 5, damage_source = treating_plunger) + + return TRUE + +/datum/wound/blunt/robotic/secures_internals/critical/handle_percussive_maintenance_success(attacking_item, mob/living/user) + var/your_or_other = (user == victim ? "your" : "[victim]'s") + victim.visible_message(span_green("[victim]'s [limb.plaintext_zone] gets smashed into a proper shape!"), \ + span_green("Your [limb.plaintext_zone] gets smashed into a proper shape!")) + + var/user_message = "[capitalize(your_or_other)] [limb.plaintext_zone]'s superstructure has been reset! Your next step is to screwdriver/wrench the internals, \ + though if you're desperate enough to use percussive maintenance, you might want to either use a crowbar or bone gel..." + to_chat(user, span_green(user_message)) + + set_superstructure_status(TRUE) + +/datum/wound/blunt/robotic/secures_internals/critical/handle_percussive_maintenance_failure(attacking_item, mob/living/user) + to_chat(victim, span_danger("Your [limb.plaintext_zone] only deforms more from the impact...")) + limb.receive_damage(brute = 1, damage_source = attacking_item, wound_bonus = CANT_WOUND) + +/datum/wound/blunt/robotic/secures_internals/critical/uses_percussive_maintenance() + return (!superstructure_remedied && limb_malleable()) + +/// Transitions our steps by setting both superstructure and secure internals readiness. +/datum/wound/blunt/robotic/secures_internals/critical/proc/set_superstructure_status(remedied) + superstructure_remedied = remedied + ready_to_secure_internals = remedied diff --git a/modular_skyrat/modules/medical/code/wounds/synth/blunt/secures_internals.dm b/modular_skyrat/modules/medical/code/wounds/synth/blunt/secures_internals.dm new file mode 100644 index 00000000000..9dd515813ef --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/synth/blunt/secures_internals.dm @@ -0,0 +1,388 @@ +/// A subtype of blunt wounds that has a "secure internals" step +/datum/wound/blunt/robotic/secures_internals + /// Our current counter for gel + gauze regeneration + var/regen_time_elapsed = 0 SECONDS + /// Time needed for gel to secure internals. + var/regen_time_needed = 30 SECONDS + + /// If we have used bone gel to secure internals. + var/gelled = FALSE + /// Total brute damage taken over the span of [regen_time_needed] deciseconds when we gel our limb. + var/gel_damage = 10 // brute in total + + /// If we are ready to begin screwdrivering or gelling our limb. + var/ready_to_secure_internals = FALSE + /// If our external plating has been torn open and we can access our internals without a tool + var/crowbarred_open = FALSE + /// If internals are secured, and we are ready to weld our limb closed and end the wound + var/ready_to_resolder = TRUE + +/datum/wound/blunt/robotic/secures_internals/handle_process(seconds_per_tick, times_fired) + . = ..() + + if (!victim || IS_IN_STASIS(victim)) + return + + if (gelled) + regen_time_elapsed += ((seconds_per_tick SECONDS) / 2) + if(victim.body_position == LYING_DOWN) + if(SPT_PROB(30, seconds_per_tick)) + regen_time_elapsed += 1 SECONDS + if(victim.IsSleeping() && SPT_PROB(30, seconds_per_tick)) + regen_time_elapsed += 1 SECONDS + + var/effective_damage = ((gel_damage / (regen_time_needed / 10)) * seconds_per_tick) + var/obj/item/stack/gauze = limb.current_gauze + if (gauze) + effective_damage *= gauze.splint_factor + limb.receive_damage(effective_damage, wound_bonus = CANT_WOUND, damage_source = src) + if(effective_damage && prob(33)) + var/gauze_text = (gauze?.splint_factor ? ", although the [gauze] helps to prevent some of the leakage" : "") + to_chat(victim, span_danger("Your [limb.plaintext_zone] sizzles as some gel leaks and warps the exterior metal[gauze_text]...")) + + if(regen_time_elapsed > regen_time_needed) + if(!victim || !limb) + qdel(src) + return + to_chat(victim, span_green("The gel within your [limb.plaintext_zone] has fully hardened, allowing you to re-solder it!")) + gelled = FALSE + ready_to_resolder = TRUE + ready_to_secure_internals = FALSE + set_disabling(FALSE) + +/datum/wound/blunt/robotic/secures_internals/modify_desc_before_span(desc) + . = ..() + + var/use_exclamation = FALSE + + if (!limb.current_gauze) // gauze covers it up + if (crowbarred_open) + . += ", [span_notice("and is violently torn open, internals visible to the outside")]" + use_exclamation = TRUE + if (gelled) + . += ", [span_notice("with fizzling blue surgical gel leaking out of the cracks")]" + use_exclamation = TRUE + if (use_exclamation) + . += "!" + +/datum/wound/blunt/robotic/secures_internals/get_scanner_description(mob/user) + . = ..() + + var/to_add = get_wound_status() + if (!isnull(to_add)) + . += "\nWound status: [to_add]" + +/datum/wound/blunt/robotic/secures_internals/get_simple_scanner_description(mob/user) + . = ..() + + var/to_add = get_wound_status() + if (!isnull(to_add)) + . += "\nWound status: [to_add]" + +/// Returns info specific to the dynamic state of the wound. +/datum/wound/blunt/robotic/secures_internals/proc/get_wound_status(mob/user) + if (crowbarred_open) + . += "The limb has been torn open, allowing ease of access to internal components, but also disabling it. " + if (gelled) + . += "Bone gel has been applied, causing progressive corrosion of the metal, but eventually securing the internals. " + +/datum/wound/blunt/robotic/secures_internals/item_can_treat(obj/item/potential_treater, mob/user) + if (potential_treater.tool_behaviour == TOOL_WELDER || potential_treater.tool_behaviour == TOOL_CAUTERY) + if (ready_to_resolder) + return TRUE + + if (ready_to_secure_internals) + if (item_can_secure_internals(potential_treater)) + return TRUE + + return ..() + +/datum/wound/blunt/robotic/secures_internals/treat(obj/item/potential_treater, mob/user) + if (ready_to_secure_internals) + if (istype(potential_treater, /obj/item/stack/medical/bone_gel)) + return apply_gel(potential_treater, user) + else if (!crowbarred_open && potential_treater.tool_behaviour == TOOL_CROWBAR) + return crowbar_open(potential_treater, user) + else if (item_can_secure_internals(potential_treater)) + return secure_internals_normally(potential_treater, user) + else if (ready_to_resolder && (potential_treater.tool_behaviour == TOOL_WELDER) || (potential_treater.tool_behaviour == TOOL_CAUTERY)) + return resolder(potential_treater, user) + + return ..() + +/// Returns TRUE if the item can be used in our 1st step (2nd if T3) of repairs. +/datum/wound/blunt/robotic/secures_internals/proc/item_can_secure_internals(obj/item/potential_treater) + return (potential_treater.tool_behaviour == TOOL_SCREWDRIVER || potential_treater.tool_behaviour == TOOL_WRENCH || istype(potential_treater, /obj/item/stack/medical/bone_gel)) + +#define CROWBAR_OPEN_SELF_TEND_DELAY_MULT 2 +#define CROWBAR_OPEN_KNOWS_ROBO_WIRES_DELAY_MULT 0.5 +#define CROWBAR_OPEN_KNOWS_ENGI_WIRES_DELAY_MULT 0.5 +#define CROWBAR_OPEN_HAS_DIAG_HUD_DELAY_MULT 0.5 +#define CROWBAR_OPEN_WOUND_SCANNED_DELAY_MULT 0.5 +/// If our limb is essential, damage dealt to it by tearing it open will be multiplied against this. +#define CROWBAR_OPEN_ESSENTIAL_LIMB_DAMAGE_MULT 1.5 + +/// The "power" put into electrocute_act whenever someone gets shocked when they crowbar open our limb +#define CROWBAR_OPEN_SHOCK_POWER 20 +/// The brute damage done to this limb (doubled on essential limbs) when it is crowbarred open +#define CROWBAR_OPEN_BRUTE_DAMAGE 20 + +/** + * Available during the "secure internals" step of T2 and T3. Requires a crowbar. Low-quality ghetto option. + * + * Tears open the limb, exposing internals. This massively increases the chance of secure internals succeeding, and removes the self-tend malice. + * + * Deals significant damage to the limb, and shocks the user (causing failure) if victim is alive, this limb is wired, and user is not insulated. + */ +/datum/wound/blunt/robotic/secures_internals/proc/crowbar_open(obj/item/crowbarring_item, mob/living/user) + if (!crowbarring_item.tool_start_check()) + return TRUE + + var/delay_mult = 1 + if (user == victim) + delay_mult *= CROWBAR_OPEN_SELF_TEND_DELAY_MULT + + var/knows_wires = FALSE + if (HAS_TRAIT(user, TRAIT_KNOW_ROBO_WIRES)) + delay_mult *= CROWBAR_OPEN_KNOWS_ROBO_WIRES_DELAY_MULT + knows_wires = TRUE + else if (HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES)) + delay_mult *= CROWBAR_OPEN_KNOWS_ENGI_WIRES_DELAY_MULT + knows_wires = TRUE + if (HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD)) + if (knows_wires) + delay_mult *= (CROWBAR_OPEN_HAS_DIAG_HUD_DELAY_MULT * 1.5) + else + delay_mult *= CROWBAR_OPEN_HAS_DIAG_HUD_DELAY_MULT + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + delay_mult *= CROWBAR_OPEN_WOUND_SCANNED_DELAY_MULT + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + + var/self_message = span_warning("You start prying open [your_or_other] [limb.plaintext_zone] with [crowbarring_item]...") + + user?.visible_message(span_bolddanger("[user] starts prying open [their_or_other] [limb.plaintext_zone] with [crowbarring_item]!"), self_message, ignored_mobs = list(victim)) + + var/victim_message + if (user != victim) // this exists so we can do a userdanger + victim_message = span_userdanger("[user] starts prying open your [limb.plaintext_zone] with [crowbarring_item]!") + else + victim_message = self_message + to_chat(victim, victim_message) + + playsound(get_turf(crowbarring_item), 'sound/machines/airlock_alien_prying.ogg', 30, TRUE) + if (!crowbarring_item.use_tool(target = victim, user = user, delay = (7 SECONDS * delay_mult), volume = 50, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + var/limb_can_shock = (victim.stat != DEAD && limb.biological_state & BIO_WIRED) + var/stunned = FALSE + + var/message + + if (user && limb_can_shock) + var/electrocute_flags = (SHOCK_KNOCKDOWN|SHOCK_NO_HUMAN_ANIM|SHOCK_SUPPRESS_MESSAGE) + var/stun_chance = 100 + + if (HAS_TRAIT(user, TRAIT_SHOCKIMMUNE)) + stun_chance = 0 + + else if (iscarbon(user)) // doesn't matter if we're shock immune, it's set to 0 anyway + var/mob/living/carbon/carbon_user = user + if (carbon_user.gloves) + stun_chance *= carbon_user.gloves.siemens_coefficient + + if (ishuman(user)) + var/mob/living/carbon/human/human_user = user + stun_chance *= human_user.physiology.siemens_coeff + stun_chance *= carbon_user.dna.species.siemens_coeff + + if (stun_chance && prob(stun_chance)) + electrocute_flags &= ~SHOCK_KNOCKDOWN + electrocute_flags &= ~SHOCK_NO_HUMAN_ANIM + stunned = TRUE + + message = span_boldwarning("[user] is shocked by [their_or_other] [limb.plaintext_zone], [user.p_their()] crowbar slipping as [user.p_they()] briefly convulse!") + self_message = span_userdanger("You are shocked by [your_or_other] [limb.plaintext_zone], causing your crowbar to slip out!") + if (user != victim) + victim_message = span_userdanger("[user] is shocked by your [limb.plaintext_zone] in [user.p_their()] efforts to tear it open!") + + var/shock_damage = CROWBAR_OPEN_SHOCK_POWER + if (limb.current_gauze) + shock_damage *= limb.current_gauze.splint_factor // always good to let gauze do something + user.electrocute_act(shock_damage, limb, flags = electrocute_flags) + + if (!stunned) + var/other_shock_text = "" + var/self_shock_text = "" + if (!limb_can_shock) + other_shock_text = ", and is striken by golden bolts of electricity" + self_shock_text = ", but are immediately shocked by the electricity contained within" + message = span_boldwarning("[user] tears open [their_or_other] [limb.plaintext_zone] with [user.p_their()] crowbar[other_shock_text]!") + self_message = span_warning("You tear open [your_or_other] [limb.plaintext_zone] with your crowbar[self_shock_text]!") + if (user != victim) + victim_message = span_userdanger("Your [limb.plaintext_zone] fragments and splinters as [user] tears it open with [user.p_their()] crowbar!") + + playsound(get_turf(crowbarring_item), 'sound/effects/bang.ogg', 35, TRUE) // we did it! + to_chat(user, span_green("You've torn [your_or_other] [limb.plaintext_zone] open, heavily damaging it but making it a lot easier to screwdriver the internals!")) + var/damage = CROWBAR_OPEN_BRUTE_DAMAGE + if (limb_essential()) // can't be disabled + damage *= CROWBAR_OPEN_ESSENTIAL_LIMB_DAMAGE_MULT + limb.receive_damage(brute = CROWBAR_OPEN_BRUTE_DAMAGE, wound_bonus = CANT_WOUND, damage_source = crowbarring_item) + set_torn_open(TRUE) + + if (user == victim) + victim_message = self_message + + user.visible_message(message, self_message, ignored_mobs = list(victim)) + to_chat(victim, victim_message) + return TRUE + +#undef CROWBAR_OPEN_SELF_TEND_DELAY_MULT +#undef CROWBAR_OPEN_KNOWS_ROBO_WIRES_DELAY_MULT +#undef CROWBAR_OPEN_KNOWS_ENGI_WIRES_DELAY_MULT +#undef CROWBAR_OPEN_HAS_DIAG_HUD_DELAY_MULT +#undef CROWBAR_OPEN_WOUND_SCANNED_DELAY_MULT +#undef CROWBAR_OPEN_ESSENTIAL_LIMB_DAMAGE_MULT + +#undef CROWBAR_OPEN_BRUTE_DAMAGE +#undef CROWBAR_OPEN_SHOCK_POWER + +/// Sets [crowbarred_open] to the new value. If we werent originally disabling, or if we arent currently and we're torn open, we set disabling to true. +/datum/wound/blunt/robotic/secures_internals/proc/set_torn_open(torn_open_state) + // if we aren't disabling but we were torn open, OR if we aren't disabling by default + var/should_update_disabling = ((!disabling && torn_open_state) || !initial(disabling)) + + crowbarred_open = torn_open_state + if(should_update_disabling) + set_disabling(torn_open_state) + +/// If, on a secure internals attempt, we have less than this chance to succeed, we warn the user. +#define SECURE_INTERNALS_CONFUSED_CHANCE_THRESHOLD 25 +#define SECURE_INTERNALS_FAILURE_BRUTE_DAMAGE 5 + +/** + * The primary way of performing the secure internals step for T2/T3. Uses a screwdriver/wrench. Very hard to do by yourself, or without a diag hud/wire knowledge. + * Roboticists/engineers have a very high chance of succeeding. + * Deals some brute damage on failure, but moves to the final step of treatment (re-soldering) on success. + * + * If [crowbarred_open], made far more likely and remove the self-tend malice. + */ +/datum/wound/blunt/robotic/secures_internals/proc/secure_internals_normally(obj/item/securing_item, mob/user) + if (!securing_item.tool_start_check()) + return TRUE + + var/chance = 10 + var/delay_mult = 1 + + if (user == victim) + if (!crowbarred_open) + chance *= 0.2 + delay_mult *= 2 + + var/knows_wires = FALSE + if (crowbarred_open) + chance *= 4 // even self-tends get a high chance of success if torn open! + if (HAS_TRAIT(user, TRAIT_KNOW_ROBO_WIRES)) + chance *= 8 // almost guaranteed if its not self surgery - guaranteed with diag hud + delay_mult *= 0.75 + knows_wires = TRUE + else if (HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES)) + chance *= 5.5 + delay_mult *= 0.85 + knows_wires = TRUE + if (HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD)) + if (knows_wires) + chance *= 1.25 // ((10 * 8) * 1.25) = 100% + else + chance *= 4 + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + chance *= 1.5 // youre not intended to fix this by yourself this way + delay_mult *= 0.8 + + var/confused = (chance < SECURE_INTERNALS_CONFUSED_CHANCE_THRESHOLD) // generate chance beforehand, so we can use this var + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + user?.visible_message(span_notice("[user] begins the delicate operation of securing the internals of [their_or_other] [limb.plaintext_zone]..."), \ + span_notice("You begin the delicate operation of securing the internals of [your_or_other] [limb.plaintext_zone]...")) + if (confused) + to_chat(user, span_warning("You are confused by the layout of [your_or_other] [limb.plaintext_zone]! A diagnostic hud would help, as would knowing robo/engi wires! You could also tear the limb open with a crowbar, or get someone else to help.")) + + if (!securing_item.use_tool(target = victim, user = user, delay = (10 SECONDS * delay_mult), volume = 50, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + if (prob(chance)) + user?.visible_message(span_green("[user] finishes securing the internals of [their_or_other] [limb.plaintext_zone]!"), \ + span_green("You finish securing the internals of [your_or_other] [limb.plaintext_zone]!")) + to_chat(user, span_green("[capitalize(your_or_other)] [limb.plaintext_zone]'s internals are now secure! Your next step is to weld/cauterize it.")) + ready_to_secure_internals = FALSE + ready_to_resolder = TRUE + else + user?.visible_message(span_danger("[user] screws up and accidentally damages [their_or_other] [limb.plaintext_zone]!")) + limb.receive_damage(brute = SECURE_INTERNALS_FAILURE_BRUTE_DAMAGE, damage_source = securing_item, wound_bonus = CANT_WOUND) + + return TRUE + +#undef SECURE_INTERNALS_CONFUSED_CHANCE_THRESHOLD +#undef SECURE_INTERNALS_FAILURE_BRUTE_DAMAGE + +/** + * "Premium" ghetto option of the secure internals step for T2/T3. Requires bone gel. Guaranteed to work. + * Deals damage over time and disables the limb, but finishes the step afterwards. + */ +/datum/wound/blunt/robotic/secures_internals/proc/apply_gel(obj/item/stack/medical/bone_gel/gel, mob/user) + if (gelled) + to_chat(user, span_warning("[user == victim ? "Your" : "[victim]'s"] [limb.plaintext_zone] is already filled with bone gel!")) + return TRUE + + var/delay_mult = 1 + if (victim == user) + delay_mult *= 0.5 + + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + delay_mult *= 0.75 + + user.visible_message(span_danger("[user] begins hastily applying [gel] to [victim]'s [limb.plaintext_zone]..."), span_warning("You begin hastily applying [gel] to [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone], disregarding the acidic effect it seems to have on the metal...")) + + if (!do_after(user, (8 SECONDS * delay_mult), target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + gel.use(1) + if(user != victim) + user.visible_message(span_notice("[user] finishes applying [gel] to [victim]'s [limb.plaintext_zone], emitting a fizzing noise!"), span_notice("You finish applying [gel] to [victim]'s [limb.plaintext_zone]!"), ignored_mobs=victim) + to_chat(victim, span_userdanger("[user] finishes applying [gel] to your [limb.plaintext_zone], and you can hear the sizzling of the metal...")) + else + victim.visible_message(span_notice("[victim] finishes applying [gel] to [victim.p_their()] [limb.plaintext_zone], emitting a funny fizzing sound!"), span_notice("You finish applying [gel] to your [limb.plaintext_zone], and you can hear the sizzling of the metal...")) + + gelled = TRUE + set_disabling(TRUE) + processes = TRUE + return TRUE + +/** + * The final step of T2/T3, requires a welder/cautery. Guaranteed to work. Cautery is slower. + * Once complete, removes the wound entirely. + */ +/datum/wound/blunt/robotic/secures_internals/proc/resolder(obj/item/welding_item, mob/user) + if (!welding_item.tool_start_check()) + return TRUE + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + victim.visible_message(span_notice("[user] begins re-soldering [their_or_other] [limb.plaintext_zone]..."), \ + span_notice("You begin re-soldering [your_or_other] [limb.plaintext_zone]...")) + + var/delay_mult = 1 + if (welding_item.tool_behaviour == TOOL_CAUTERY) + delay_mult *= 3 // less efficient + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + delay_mult *= 0.75 + + if (!welding_item.use_tool(target = victim, user = user, delay = 7 SECONDS * delay_mult, volume = 50, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + victim.visible_message(span_green("[user] finishes re-soldering [their_or_other] [limb.plaintext_zone]!"), \ + span_notice("You finish re-soldering [your_or_other] [limb.plaintext_zone]!")) + remove_wound() + return TRUE diff --git a/modular_skyrat/modules/medical/code/wounds/synth/robotic_burns.dm b/modular_skyrat/modules/medical/code/wounds/synth/robotic_burns.dm new file mode 100644 index 00000000000..24ac18eeab6 --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/synth/robotic_burns.dm @@ -0,0 +1,441 @@ +#define OVERHEAT_ON_STASIS_HEAT_MULT 0.25 +/// At 100% hercuri composition, a spray of reagents will have its effective chem temp reduced by this. 50%, reduced by half this, etc. +#define ROBOTIC_BURN_REAGENT_EXPOSURE_HERCURI_MAX_HEAT_DECREMENT 60 +/// At 100% hercuri composition, a spray of reagents will have its heat shock damage reduced by this. 50%, reduced by half this, etc. +#define ROBOTIC_BURN_REAGENT_EXPOSURE_HERCURI_HEAT_SHOCK_MULT_DECREMENT 0.3 + +/datum/wound_pregen_data/burnt_metal + abstract = TRUE + required_limb_biostate = BIO_METAL + required_wounding_types = list(WOUND_BURN) + wound_series = WOUND_SERIES_METAL_BURN_OVERHEAT + +/datum/wound_pregen_data/burnt_metal/generate_scar_priorities() + return list("[BIO_METAL]") + +/datum/wound/burn/robotic/overheat + treat_text = "Introduction of a cold environment or lowering of body temperature." + + simple_desc = "Metals are overheated, increasing damage taken significantly and raising body temperature!" + simple_treat_text = "Ideally cryogenics, but any source of low body temperature can work. Spraying with spray bottles/extinguishers/showers \ + will quickly cool the limb, but cause damage. Hercuri is especially effective in quick cooling. \ + Clothing reduces the water/hercuri that makes it to the metal, and gauze binds it and reduces the damage taken." + homemade_treat_text = "You can also splash any liquid on it for a rather inefficient and damaging coolant!" + + default_scar_file = METAL_SCAR_FILE + + wound_flags = (ACCEPTS_GAUZE|SPLINT_OVERLAY|CAN_BE_GRASPED) // gauze binds the metal and makes it resistant to thermal shock + + processes = TRUE + + /// The virtual temperature of the chassis. Crucial for many things, like our severity, the temp we transfer, our cooling damage, etc. + var/chassis_temperature + + /// The lower bound of the chassis_temperature we can start with. + var/starting_temperature_min = (BODYTEMP_NORMAL + 200) + /// The upper bound of the chassis_temperature we can start with. + var/starting_temperature_max = (BODYTEMP_NORMAL + 250) + + /// If [chassis_temperature] goes below this, we reduce in severity. + var/cooling_threshold = (BODYTEMP_NORMAL + 3) + /// If [chassis_temperature] goes above this, we increase in severity. + var/heating_threshold = (BODYTEMP_NORMAL + 300) + + /// The buffer in kelvin we will subtract from the chassis_temperature of a wound we demote to. + var/cooling_demote_buffer = 60 + /// The buffer in kelvin we will add to the chassis_temperature of a wound we promote to. + var/heating_promote_buffer = 60 + + /// The coefficient of heat transfer we will use when shifting our temp to the victim's. + var/bodytemp_coeff = 0.04 + /// For every degree below normal bodytemp, we will multiply our incoming temperature by 1 + degrees * this. Allows incentivization of freezing yourself instead of just waiting. + var/bodytemp_difference_expose_bonus_ratio = 0.035 + /// The coefficient of heat transfer we will use when shifting our victim's temp to ours. + var/outgoing_bodytemp_coeff = 0 + /// The mult applied to heat output when we are on a important limb, e.g. head/torso. + var/important_outgoing_mult = 1.2 + /// The coefficient of heat transfer we will use when shifting our temp to a turf. + var/turf_coeff = 0.02 + + /// The maximum temperature we can cause by heating our victim. + var/max_outgoing_temperature = BODYTEMP_HEAT_WOUND_LIMIT - 1 + + /// If we are hit with burn damage, the damage will be multiplied against this to determine the effective heat we get. + var/incoming_damage_heat_coeff = 3 + + /// The coefficient of heat transfer we will use when receiving heat from reagent contact. + var/base_reagent_temp_coefficient = 0.02 + + /// The ratio of temp shift -> brute damage. Careful with this value, it can make stuff really really nasty. + var/heat_shock_delta_to_damage_ratio = 0.12 + /// The minimum heat difference we must have on reagent contact to cause heat shock damage. + var/heat_shock_minimum_delta = 5 + + /// If we are sprayed with a extinguisher/shower with obscuring clothing on (think clothing that prevents surgery), the effect is multiplied against this. + var/sprayed_with_reagent_clothed_mult = 0.15 + + /// The wound we demote to when we go below cooling threshold. If null, removes us. + var/datum/wound/burn/robotic/demotes_to + /// The wound we promote to when we go above heating threshold. + var/datum/wound/burn/robotic/promotes_to + + /// The color of the light we will generate. + var/light_color + /// The power of the light we will generate. + var/light_power + /// The range of the light we will generate. + var/light_range + + /// The glow we have attached to our victim, to simulate our limb glowing. + var/obj/effect/dummy/lighting_obj/moblight/mob_glow + +/datum/wound/burn/robotic/overheat/New(temperature) + chassis_temperature = (isnull(temperature) ? get_random_starting_temperature() : temperature) + + return ..() + +/datum/wound/burn/robotic/overheat/Destroy() + QDEL_NULL(mob_glow) + return ..() + +/datum/wound/burn/robotic/overheat/set_victim(mob/living/new_victim) + if (victim) + QDEL_NULL(mob_glow) + UnregisterSignal(victim, COMSIG_MOB_AFTER_APPLY_DAMAGE) + UnregisterSignal(victim, COMSIG_ATOM_AFTER_EXPOSE_REAGENTS) + if (new_victim) + mob_glow = new_victim.mob_light(light_range, light_power, light_color) + mob_glow.set_light_on(TRUE) + RegisterSignal(new_victim, COMSIG_MOB_AFTER_APPLY_DAMAGE, PROC_REF(victim_attacked)) + RegisterSignal(new_victim, COMSIG_ATOM_AFTER_EXPOSE_REAGENTS, PROC_REF(victim_exposed_to_reagents)) + + return ..() + +/datum/wound/burn/robotic/overheat/proc/get_random_starting_temperature() + return LERP(starting_temperature_min, starting_temperature_max, rand()) // LERP since we deal with decimals + +/datum/wound/burn/robotic/get_limb_examine_description() + return span_warning("The metal on this limb is glowing radiantly.") + +/datum/wound/burn/robotic/overheat/handle_process(seconds_per_tick, times_fired) + if (isnull(victim)) + var/turf/our_turf = get_turf(limb) + if (!isnull(our_turf)) + expose_temperature(our_turf.GetTemperature(), (turf_coeff * seconds_per_tick)) + return + if (outgoing_bodytemp_coeff <= 0) + return + var/statis_mult = 1 + if (IS_IN_STASIS(victim)) // stasis heavily reduces the ingoing and outgoing transfer of heat + statis_mult *= OVERHEAT_ON_STASIS_HEAT_MULT + + var/difference_from_average = max((BODYTEMP_NORMAL - victim.bodytemperature), 0) + var/difference_mult = 1 + (difference_from_average * bodytemp_difference_expose_bonus_ratio) + if (expose_temperature(victim.bodytemperature, (bodytemp_coeff * seconds_per_tick * statis_mult * difference_mult))) + return + var/mult = outgoing_bodytemp_coeff + if (limb_essential()) + mult *= important_outgoing_mult + var/adjustment_allowed = max((max_outgoing_temperature - victim.bodytemperature), 0) + var/amount_to_adjust = min((((chassis_temperature - victim.bodytemperature) * mult) * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick * statis_mult), adjustment_allowed) + victim.adjust_bodytemperature(amount_to_adjust) + +/// Signal proc for when our victim is externally attacked. Increases chassis temp based on burn damage received. +/datum/wound/burn/robotic/overheat/proc/victim_attacked(datum/source, damage, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item) + SIGNAL_HANDLER + + if (def_zone != limb.body_zone) // use this proc since receive damage can also be called for like, chems and shit + return + + if (!victim) + return + + if (damagetype != BURN) + return + + if (wound_bonus == CANT_WOUND) + return + + var/effective_damage = (damage - blocked) + if (effective_damage <= 0) + return + + expose_temperature((chassis_temperature + effective_damage), incoming_damage_heat_coeff) + +/** + * Signal proc for when our victim is exposed to reagents, obviously. + * + * Equalizes temp to the reagent temp, but also causes thermal shock. Basically, does damage based on the temp differential. + * Clothes reduce the effects massively. Hercuri reduces the thermal shock and gets a special temp buff. + */ +/datum/wound/burn/robotic/overheat/proc/victim_exposed_to_reagents(datum/signal_source, list/reagents, datum/reagents/source, methods, volume_modifier, show_message) + SIGNAL_HANDLER + + var/reagent_coeff = base_reagent_temp_coefficient + if (!get_location_accessible(victim, limb.body_zone)) + if (ishuman(victim)) + // hi! its niko! small rant + // this proc has no goddamn reason to be on human, it could so easily just have used a proc on carbon that would get the required bodyparts to check + // but no. it had to hardcode the list in the proc itself so its impossible to modularly fix this + // so instead we just say fuck it and hope to god only human subtypes get this wound + // tldr; ryll why + var/mob/living/carbon/human/human_victim = victim + for (var/obj/item/clothing/iter_clothing as anything in human_victim.get_clothing_on_part(limb)) + if (iter_clothing.clothing_flags & THICKMATERIAL) + return + + reagent_coeff *= sprayed_with_reagent_clothed_mult + + if (istype(source.my_atom, /obj/effect/particle_effect/water/extinguisher)) // this used to be a lot, lot more modular, but sadly reagent temps/volumes and shit are horribly inconsistant + expose_temperature(source.chem_temp, (2.55 * reagent_coeff), TRUE) + return + + if (istype(source.my_atom, /obj/machinery/shower)) + expose_temperature(source.chem_temp, (15 * volume_modifier * reagent_coeff), TRUE) + return + + var/total_reagent_amount = 0 + var/hercuri_amount = 0 + for (var/datum/reagent/iterated_reagent as anything in reagents) + total_reagent_amount += reagents[iterated_reagent] + if (iterated_reagent.type == /datum/reagent/medicine/c2/hercuri) + hercuri_amount = reagents[iterated_reagent] + + var/hercuri_percent = (hercuri_amount / total_reagent_amount) + + var/hercuri_chem_temp_increment = (ROBOTIC_BURN_REAGENT_EXPOSURE_HERCURI_MAX_HEAT_DECREMENT * hercuri_percent) + var/local_chem_temp = max(source.chem_temp - hercuri_chem_temp_increment, 0) + + var/heat_shock_damage_mult = 1 - (ROBOTIC_BURN_REAGENT_EXPOSURE_HERCURI_HEAT_SHOCK_MULT_DECREMENT * hercuri_percent) + + expose_temperature(local_chem_temp, (reagent_coeff * volume_modifier * total_reagent_amount), TRUE, heat_shock_damage_mult = heat_shock_damage_mult) + +/// Adjusts chassis_temperature by the delta between temperature and itself, multiplied by coeff. +/// If heat_shock is TRUE, limb will receive brute damage based on the delta. +/datum/wound/burn/robotic/overheat/proc/expose_temperature(temperature, coeff = 0.02, heat_shock = FALSE, heat_shock_damage_mult = 1) + var/temp_delta = (temperature - chassis_temperature) * coeff + + var/unclamped_new_temperature = (chassis_temperature + temp_delta) + var/clamped_new_temperature + var/heat_adjustment_used + + if(temp_delta > 0) + clamped_new_temperature = min(min(chassis_temperature + max(temp_delta, 1), temperature), heating_threshold) + heat_adjustment_used = (clamped_new_temperature / unclamped_new_temperature) + else + clamped_new_temperature = max(max(chassis_temperature + min(temp_delta, -1), temperature), cooling_threshold) + heat_adjustment_used = (unclamped_new_temperature / clamped_new_temperature) + + if (heat_shock && abs(temp_delta) > heat_shock_minimum_delta) + var/gauze_mult = 1 + var/obj/item/stack/gauze = limb.current_gauze + if (gauze) + gauze_mult *= (gauze.splint_factor) * 0.4 // very very effective + + if (limb.grasped_by) + gauze_mult *= 0.7 // hold it down yourself + + if (victim) + var/gauze_or_not = (!isnull(gauze) ? ", but [gauze] helps to keep it together" : "") + var/clothing_text = (!get_location_accessible(victim, limb.body_zone) ? ", [victim.p_their()] clothing absorbing some of the liquid" : "") + victim.visible_message(span_warning("[victim]'s [limb.plaintext_zone] strains from the thermal shock[clothing_text][gauze_or_not]!")) + playsound(victim, 'sound/items/welder.ogg', 25) + + var/damage = (((abs(temp_delta) * heat_shock_delta_to_damage_ratio) * gauze_mult) * heat_shock_damage_mult) * heat_adjustment_used + limb.receive_damage(brute = damage, wound_bonus = CANT_WOUND) + + chassis_temperature = clamped_new_temperature // can only be decimal or 1, so it can only reduce the intensity of the adjustment + + return check_temperature() + +/// Removes, demotes, or promotes ourselves to a new wound type if our temperature is past a heating/cooling threshold. +/datum/wound/burn/robotic/overheat/proc/check_temperature() + if (chassis_temperature <= cooling_threshold) + if (demotes_to) + victim.visible_message(span_green("[victim]'s [limb.plaintext_zone] turns a more pleasant thermal color as it cools down a little..."), span_green("Your [limb.plaintext_zone] seems to cool down a little!")) + replace_wound(new demotes_to(cooling_threshold - cooling_demote_buffer)) + return TRUE + else + victim.visible_message(span_green("[victim]'s [limb.plaintext_zone] simmers gently as it returns to its usual colors!"), span_green("Your [limb.plaintext_zone] simmers gently as it returns to its usual colors!")) + remove_wound() + return TRUE + else if (promotes_to && chassis_temperature >= heating_threshold) + victim.visible_message(span_danger("[victim]'s [limb.plaintext_zone] brightens as it overheats further!"), span_userdanger("Your [limb.plaintext_zone] sizzles and brightens as it overheats further!")) + replace_wound(new promotes_to(heating_threshold + heating_promote_buffer)) + return TRUE + +/// Returns a string with our temperature and heating/cooling thresholds, for use in health analyzers. +/datum/wound/burn/robotic/overheat/proc/get_wound_status_info() + var/current_temp_celcius = round(chassis_temperature - T0C, 0.1) + var/current_temp_fahrenheit = round(chassis_temperature * 1.8-459.67, 0.1) + + var/cool_celcius = round(cooling_threshold - T0C, 0.1) + var/cool_fahrenheit = round(cooling_threshold * 1.8-459.67, 0.1) + + var/heat_celcius = round(heating_threshold - T0C, 0.1) + var/heat_fahrenheit = round(heating_threshold * 1.8-459.67, 0.1) + + return "Its current temperature is [span_blue("[current_temp_celcius ] °C ([current_temp_fahrenheit] °F)")], \ + and needs to cool to [span_nicegreen("[cool_celcius] °C ([cool_fahrenheit] °F)")], but \ + will worsen if heated to [span_purple("[heat_celcius] °C ([heat_fahrenheit] °F)")]." + +/datum/wound/burn/robotic/overheat/get_scanner_description(mob/user) + . = ..() + + . += "\nWound status: [get_wound_status_info()]" + +/datum/wound/burn/robotic/overheat/get_simple_scanner_description(mob/user) + . = ..() + + . += "\nWound status: [get_wound_status_info()]" + +// this wound is unaffected by cryoxadone and pyroxadone +/datum/wound/burn/robotic/overheat/on_xadone(power) + return + +/datum/wound/burn/robotic/overheat/moderate + name = "Transient Overheating" + desc = "External metals have exceeded lower-bound thermal limits and have lost some structural integrity, increasing damage taken as well as the chance to \ + sustain additional wounds." + occur_text = "lets out a slight groan as it turns a dull shade of thermal red" + examine_desc = "is glowing a dull thermal red and giving off heat" + treat_text = "Reduction of body temperature to expedite the passive heat dissipation - or, if thermal shock is to be risked, application of a fire extinguisher/shower." + severity = WOUND_SEVERITY_MODERATE + + damage_multiplier_penalty = 1.15 //1.15x damage taken + + starting_temperature_min = (BODYTEMP_NORMAL + 350) + starting_temperature_max = (BODYTEMP_NORMAL + 400) + + cooling_threshold = (BODYTEMP_NORMAL + 100) + heating_threshold = (BODYTEMP_NORMAL + 500) + + cooling_demote_buffer = 60 + heating_promote_buffer = 100 + + a_or_from = "from" + + // easy to get + threshold_penalty = 30 + + status_effect_type = /datum/status_effect/wound/burn/robotic/moderate + + sound_volume = 20 + + outgoing_bodytemp_coeff = 0.0056 + bodytemp_coeff = 0.006 + + base_reagent_temp_coefficient = 0.03 + heat_shock_delta_to_damage_ratio = 0.2 + + promotes_to = /datum/wound/burn/robotic/overheat/severe + + light_color = COLOR_RED + light_power = 0.1 + light_range = 0.5 + + can_scar = FALSE + +/datum/wound_pregen_data/burnt_metal/transient_overheat + abstract = FALSE + + wound_path_to_generate = /datum/wound/burn/robotic/overheat/moderate + + threshold_minimum = 30 + +/datum/wound/burn/robotic/overheat/severe + name = "Thermal Overload" + desc = "Exterior plating has surpassed critical thermal levels, causing significant failure in structural integrity and overheating of internal systems." + occur_text = "sizzles, the externals turning a dull shade of orange" + examine_desc = "appears discolored and polychromatic, parts of it glowing a dull orange" + treat_text = "Isolation from physical hazards, and accommodation of passive heat dissipation - active cooling may be used, but temperature differentials significantly \ + raise the risk of thermal shock." + severity = WOUND_SEVERITY_SEVERE + + a_or_from = "from" + + threshold_penalty = 65 + + status_effect_type = /datum/status_effect/wound/burn/robotic/severe + damage_multiplier_penalty = 1.25 // 1.25x damage taken + + starting_temperature_min = (BODYTEMP_NORMAL + 550) + starting_temperature_max = (BODYTEMP_NORMAL + 600) + + heating_promote_buffer = 150 + + cooling_threshold = (BODYTEMP_NORMAL + 375) + heating_threshold = (BODYTEMP_NORMAL + 800) + + outgoing_bodytemp_coeff = 0.0053 + bodytemp_coeff = 0.004 + + base_reagent_temp_coefficient = 0.03 + heat_shock_delta_to_damage_ratio = 0.2 + + demotes_to = /datum/wound/burn/robotic/overheat/moderate + promotes_to = /datum/wound/burn/robotic/overheat/critical + + light_color = COLOR_BRIGHT_ORANGE + light_power = 0.8 + light_range = 0.5 + + scar_keyword = "burnsevere" + +/datum/wound_pregen_data/burnt_metal/severe + abstract = FALSE + wound_path_to_generate = /datum/wound/burn/robotic/overheat/severe + threshold_minimum = 80 + +/datum/wound/burn/robotic/overheat/critical + name = "Runaway Exothermy" + desc = "Carapace is beyond melting point, causing catastrophic structural integrity failure as well as massively heating up the subject." + occur_text = "turns a bright shade of radiant white as it sizzles and melts" + examine_desc = "is a blinding shade of white, almost melting from the heat" + treat_text = "Immediate confinement to cryogenics, as rapid overheating and physical vulnerability may occur. Active cooling is not advised, \ + since the thermal shock may be lethal with such a temperature differential." + severity = WOUND_SEVERITY_CRITICAL + + a_or_from = "from" + + sound_effect = 'sound/effects/wounds/sizzle2.ogg' + + threshold_penalty = 100 + + status_effect_type = /datum/status_effect/wound/burn/robotic/critical + + damage_multiplier_penalty = 1.5 //1.5x damage taken + + starting_temperature_min = (BODYTEMP_NORMAL + 1050) + starting_temperature_max = (BODYTEMP_NORMAL + 1100) + + cooling_demote_buffer = 100 + + cooling_threshold = (BODYTEMP_NORMAL + 775) + heating_threshold = INFINITY + + outgoing_bodytemp_coeff = 0.0055 // burn... BURN... + bodytemp_coeff = 0.0025 + + base_reagent_temp_coefficient = 0.03 + heat_shock_delta_to_damage_ratio = 0.2 + + max_outgoing_temperature = BODYTEMP_HEAT_WOUND_LIMIT // critical CAN cause wounds, but only barely + + demotes_to = /datum/wound/burn/robotic/overheat/severe + + wound_flags = (MANGLES_EXTERIOR|ACCEPTS_GAUZE|SPLINT_OVERLAY|CAN_BE_GRASPED) + + light_color = COLOR_VERY_SOFT_YELLOW + light_power = 1.3 + light_range = 1.5 + + scar_keyword = "burncritical" + +/datum/wound_pregen_data/burnt_metal/critical + abstract = FALSE + wound_path_to_generate = /datum/wound/burn/robotic/overheat/critical + threshold_minimum = 140 + +#undef OVERHEAT_ON_STASIS_HEAT_MULT +#undef ROBOTIC_BURN_REAGENT_EXPOSURE_HERCURI_MAX_HEAT_DECREMENT diff --git a/modular_skyrat/modules/medical/code/wounds/synth/robotic_muscle.dm b/modular_skyrat/modules/medical/code/wounds/synth/robotic_muscle.dm new file mode 100644 index 00000000000..4e91c58f7f6 --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/synth/robotic_muscle.dm @@ -0,0 +1,47 @@ +/datum/wound/muscle/robotic + sound_effect = 'sound/effects/wounds/blood1.ogg' + +/datum/wound_pregen_data/muscle/robotic + required_limb_biostate = (BIO_METAL) + +/datum/wound/muscle/robotic/moderate + name = "Overworked Servo" + desc = "A servo has been overworked, and will operate with reduced efficiency until rested." + treat_text = "A tight splint on the affected limb, as well as plenty of rest and sleep." + examine_desc = "appears to be moving sluggishly" + occur_text = "jitters for a moment before moving sluggishly" + severity = WOUND_SEVERITY_MODERATE + interaction_efficiency_penalty = 1.5 + limp_slowdown = 2 + limp_chance = 30 + threshold_penalty = 15 + status_effect_type = /datum/status_effect/wound/muscle/robotic/moderate + regen_ticks_needed = 90 + +/datum/wound_pregen_data/muscle/robotic/servo + abstract = FALSE + wound_path_to_generate = /datum/wound/muscle/robotic/moderate + threshold_minimum = 35 + +/datum/wound/muscle/robotic/severe + name = "Exhausted Piston" + sound_effect = 'sound/effects/wounds/blood2.ogg' + desc = "An important hydraulic piston has been critically overused, resulting in total dysfunction until it recovers." + treat_text = "A tight splint on the affected limb, as well as plenty of rest and sleep." + examine_desc = "is stiffly limp, the extremities splayed out widely" + occur_text = "goes completely stiff, seeming to lock into position" + severity = WOUND_SEVERITY_SEVERE + interaction_efficiency_penalty = 2 + limp_slowdown = 5 + limp_chance = 40 + threshold_penalty = 35 + disabling = TRUE + status_effect_type = /datum/status_effect/wound/muscle/robotic/severe + regen_ticks_needed = 150 + +/datum/wound_pregen_data/muscle/robotic/hydraulic + abstract = FALSE + + wound_path_to_generate = /datum/wound/muscle/robotic/severe + threshold_minimum = 80 + diff --git a/modular_skyrat/modules/medical/code/wounds/synth/robotic_pierce.dm b/modular_skyrat/modules/medical/code/wounds/synth/robotic_pierce.dm new file mode 100644 index 00000000000..b7e4f259e5d --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/synth/robotic_pierce.dm @@ -0,0 +1,146 @@ +// Pierce +// Slow to rise but high damage overall +// Hard-ish to fix +/datum/wound/electrical_damage/pierce + heat_differential_healing_mult = 0.01 + simple_desc = "Electrical conduits have been pierced open, resulting in a fault that slowly intensifies, but with extreme maximum voltage!" + +/datum/wound_pregen_data/electrical_damage/pierce + abstract = TRUE + wound_series = WOUND_SERIES_WIRE_PIERCE_ELECTRICAL_DAMAGE + required_wounding_types = list(WOUND_PIERCE) + +/datum/wound/burn/electrical_damage/pierce/get_limb_examine_description() + return span_warning("The metal on this limb is pierced open.") + +/datum/wound/electrical_damage/pierce/moderate + name = "Punctured Capacitor" + desc = "A major capacitor has been broken open, causing slow but noticable electrical damage." + occur_text = "shoots out a short stream of sparks" + examine_desc = "is shuddering gently, movements a little weak" + treat_text = "Replacing of damaged wiring, though repairs via wirecutting instruments or sutures may suffice, albeit at limited efficiency. In case of emergency, \ + subject may be subjected to high temperatures to allow solder to reset." + + sound_effect = 'modular_skyrat/modules/medical/sound/robotic_slash_T1.ogg' + + severity = WOUND_SEVERITY_MODERATE + + sound_volume = 30 + + threshold_penalty = 30 + + intensity = 10 SECONDS + processing_full_shock_threshold = 7 MINUTES + + processing_shock_power_per_second_max = 1.2 + processing_shock_power_per_second_min = 1.1 + + processing_shock_stun_chance = 0.5 + processing_shock_spark_chance = 35 + + process_shock_spark_count_max = 1 + process_shock_spark_count_min = 1 + + wirecut_repair_percent = 0.065 // not even faster at this point + wire_repair_percent = 0.026 + + initial_sparks_amount = 1 + + status_effect_type = /datum/status_effect/wound/electrical_damage/pierce/moderate + + a_or_from = "a" + + scar_keyword = "piercemoderate" + +/datum/wound_pregen_data/electrical_damage/pierce/moderate + abstract = FALSE + wound_path_to_generate = /datum/wound/electrical_damage/pierce/moderate + threshold_minimum = 40 + +/datum/wound/electrical_damage/pierce/severe + name = "Penetrated Transformer" + desc = "A major transformer has been pierced, causing slow-to-progess but eventually intense electrical damage." + occur_text = "sputters and goes limp for a moment as it ejects a stream of sparks" + examine_desc = "is shuddering significantly, its servos briefly giving way in a rythmic pattern" + treat_text = "Containment of damaged wiring via gauze, then application of fresh wiring/sutures, or resetting of displaced wiring via wirecutter/retractor." + + sound_effect = 'modular_skyrat/modules/medical/sound/robotic_slash_T2.ogg' + + severity = WOUND_SEVERITY_SEVERE + + sound_volume = 15 + + threshold_penalty = 40 + + intensity = 20 SECONDS + processing_full_shock_threshold = 6.5 MINUTES + + processing_shock_power_per_second_max = 1.6 + processing_shock_power_per_second_min = 1.5 + + processing_shock_stun_chance = 2.5 + processing_shock_spark_chance = 60 + + process_shock_spark_count_max = 2 + process_shock_spark_count_min = 1 + + wirecut_repair_percent = 0.068 + wire_repair_percent = 0.02 + + initial_sparks_amount = 3 + + status_effect_type = /datum/status_effect/wound/electrical_damage/pierce/moderate + + a_or_from = "a" + + scar_keyword = "piercemoderate" + +/datum/wound_pregen_data/electrical_damage/pierce/severe + abstract = FALSE + wound_path_to_generate = /datum/wound/electrical_damage/pierce/severe + threshold_minimum = 60 + +/datum/wound/electrical_damage/pierce/critical + name = "Ruptured PSU" + desc = "The local PSU of this limb has suffered a core rupture, causing a progressive power failure that will slowly intensify into massive electrical damage." + occur_text = "flashes with radiant blue, emitting a noise not unlike a Jacob's Ladder" + examine_desc = "'s PSU is visible, with a sizable hole in the center" + treat_text = "Immediate securing via gauze, followed by emergency cable replacement and securing via wirecutters or hemostat. \ + If the fault has become uncontrollable, extreme heat therapy is recommended." + + severity = WOUND_SEVERITY_CRITICAL + wound_flags = (ACCEPTS_GAUZE|MANGLES_EXTERIOR|CAN_BE_GRASPED|SPLINT_OVERLAY) + + sound_effect = 'modular_skyrat/modules/medical/sound/robotic_slash_T3.ogg' + + sound_volume = 30 + + threshold_penalty = 60 + + intensity = 30 SECONDS + processing_full_shock_threshold = 5.5 MINUTES + + processing_shock_power_per_second_max = 2.2 + processing_shock_power_per_second_min = 2.1 + + processing_shock_stun_chance = 1 + processing_shock_spark_chance = 90 + + process_shock_spark_count_max = 3 + process_shock_spark_count_min = 2 + + wirecut_repair_percent = 0.067 + wire_repair_percent = 0.018 + + initial_sparks_amount = 8 + + status_effect_type = /datum/status_effect/wound/electrical_damage/pierce/moderate + + a_or_from = "a" + + scar_keyword = "piercecritical" + +/datum/wound_pregen_data/electrical_damage/pierce/critical + abstract = FALSE + wound_path_to_generate = /datum/wound/electrical_damage/pierce/critical + threshold_minimum = 110 diff --git a/modular_skyrat/modules/medical/code/wounds/synth/robotic_slash.dm b/modular_skyrat/modules/medical/code/wounds/synth/robotic_slash.dm new file mode 100644 index 00000000000..a28737520b5 --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/synth/robotic_slash.dm @@ -0,0 +1,628 @@ +/// How much damage and progress is reduced when on stasis. +#define ELECTRICAL_DAMAGE_ON_STASIS_MULT 0.15 +/// How much damage and progress is reduced when limb is grasped. +#define ELECTRICAL_DAMAGE_GRASPED_MULT 0.7 +/// How much damage and progress is reduced when our victim lies down. +#define ELECTRICAL_DAMAGE_LYING_DOWN_MULT 0.7 +/// How much progress is reduced when our victim is dead. +#define ELECTRICAL_DAMAGE_DEAD_PROGRESS_MULT 0.2 // they'll be resting to, so this is more like 0.1 + +/// Base time for a wirecutter being used. +#define ELECTRICAL_DAMAGE_WIRECUTTER_BASE_DELAY 8 SECONDS +/// Base time for a cable coil being used. +#define ELECTRICAL_DAMAGE_SUTURE_WIRE_BASE_DELAY 0.8 SECONDS +/// Global damage multiplier for the power a given electrical damage wound will add per tick. +#define ELECTRICAL_DAMAGE_POWER_PER_TICK_MULT 1 +/// Global damage multiplier for how much repairing wiring will reduce intensity. Higher is more. +#define ELECTRICAL_DAMAGE_SUTURE_WIRE_HEALING_AMOUNT_MULT 1 + +/// The minimum shock power we must have available to zap our victim. Must be at least one, since electrocute_act fails if its lower. +#define ELECTRICAL_DAMAGE_MINIMUM_SHOCK_POWER_PER_ZAP 1 +/// The maximum burn damage our limb can have before we refuse to let people who havent aggrograbbed the limb repair it with wires. This is so people can opt to just fix the burn damage. +#define ELECTRICAL_DAMAGE_MAX_BURN_DAMAGE_TO_LET_WIRES_REPAIR 5 + +/datum/wound/electrical_damage + name = "Electrical (Wires) Wound" + + simple_treat_text = "Replacing of broken wiring, or repairing via a wirecutter. Bandaging binds the wiring and reduces intensity buildup, \ + as does firmly grasping the limb - both the victim and someone else can do this. Roboticists/Engineers get a bonus to treatment, as do diagnostic HUDs." + homemade_treat_text = "Sutures can repair the wiring at reduced efficiency, as can retractors. In a pinch, high temperatures can repair the wiring!" + + wound_flags = (ACCEPTS_GAUZE|CAN_BE_GRASPED|SPLINT_OVERLAY) + + treatable_tools = list(TOOL_WIRECUTTER, TOOL_RETRACTOR) + treatable_by = list(/obj/item/stack/medical/suture) + treatable_by_grabbed = list(/obj/item/stack/cable_coil) + + default_scar_file = METAL_SCAR_FILE + + processes = TRUE + + /// How many sparks do we spawn when we're gained? + var/initial_sparks_amount = 1 + + /// How much of our damage is reduced if the target is shock immune. Percent. + var/shock_immunity_self_damage_reduction = 75 + + /// Mult for our damage if we are unimportant. + var/limb_unimportant_damage_mult = 0.8 + /// Mult for our progress if we are unimportant. + var/limb_unimportant_progress_mult = 0.8 + + /// The overall "intensity" of this wound. Goes up to [processing_full_shock_threshold], and is used for determining our effect scaling. Measured in deciseconds. + var/intensity + /// The time, in deciseconds, it takes to reach 100% power. + var/processing_full_shock_threshold = 3 MINUTES + /// If [intensity] is at or below this, we remove ourselves. + var/minimum_intensity = 0 + + /// How much shock power we add to [processing_shock_power_this_tick] per tick. Lower bound + var/processing_shock_power_per_second_min = 0.1 + /// How much shock power we add to [processing_shock_power_this_tick] per tick. Upper bound + var/processing_shock_power_per_second_max = 0.2 + + /// In the case we get below 1 power, we add the power to this buffer and use it next tick. + var/processing_shock_power_this_tick = 0 + /// The chance for each processed shock to stun the user. + var/processing_shock_stun_chance = 0 + /// The chance for each processed shock to spark. + var/processing_shock_spark_chance = 30 + /// The chance for each processed shock to message the user. + var/process_shock_message_chance = 80 + + /// Simple mult for how much of real time is added to [intensity]. + var/seconds_per_intensity_mult = 1 + + /// How many sparks we spawn if a shock sparks. Lower bound + var/process_shock_spark_count_min = 1 + /// How many sparks we spawn if a shock sparks. Upper bound + var/process_shock_spark_count_max = 1 + + // Generally should be less fast than wire, but its effectiveness should increase with severity + /// The percent, in decimal, a successful wirecut use will reduce intensity by. + var/wirecut_repair_percent + // Generally should be lower than wirecut + /// The percent, in decimal, a successful wire use will reduce intensity by. + var/wire_repair_percent + + /// The basic multiplier to all our effects. Damage, progress, etc. + var/overall_effect_mult = 1 + + /// The bodyheat our victim must be at or above to start getting passive healing. + var/heat_thresh_to_heal = (BODYTEMP_HEAT_DAMAGE_LIMIT + 30) + /// The mult that heat differences between normal and bodytemp threshold is multiplied against. Controls passive heat healing. + var/heat_differential_healing_mult = 0.08 + + /// Percent chance for a heat repair to give the victim a message. + var/heat_heal_message_chance = 20 + + /// If [get_intensity_mult()] is at or above this, the limb gets disabled. If null, it will never occur. + var/disable_at_intensity_mult + +/datum/wound_pregen_data/electrical_damage + abstract = TRUE + required_limb_biostate = (BIO_WIRED) + required_wounding_types = list(WOUND_SLASH) + wound_series = WOUND_SERIES_WIRE_SLASH_ELECTRICAL_DAMAGE + +/datum/wound_pregen_data/electrical_damage/generate_scar_priorities() + return list("[BIO_METAL]") // wire scars dont exist so we can just use metal + +/datum/wound/burn/electrical_damage/slash/get_limb_examine_description() + return span_warning("The wiring on this limb is slashed open.") + +/datum/wound/electrical_damage/handle_process(seconds_per_tick, times_fired) + . = ..() + + var/base_mult = get_base_mult() + + var/seconds_per_tick_for_intensity = seconds_per_tick * get_progress_mult() + seconds_per_tick_for_intensity = modify_progress_after_progress_mult(seconds_per_tick_for_intensity, seconds_per_tick) + + adjust_intensity(seconds_per_tick_for_intensity SECONDS) + + if (!victim || victim.stat == DEAD) + return + + var/damage_mult = get_damage_mult(victim) + var/intensity_mult = get_intensity_mult() + + damage_mult *= seconds_per_tick + damage_mult *= intensity_mult + + var/picked_damage = LERP(processing_shock_power_per_second_min, processing_shock_power_per_second_max, rand()) + processing_shock_power_this_tick += (picked_damage * damage_mult) + if (processing_shock_power_this_tick <= ELECTRICAL_DAMAGE_MINIMUM_SHOCK_POWER_PER_ZAP) + return + + var/stun_chance = (processing_shock_stun_chance * intensity_mult) * base_mult + var/spark_chance = (processing_shock_spark_chance * intensity_mult) * base_mult + + var/should_stun = SPT_PROB(stun_chance, seconds_per_tick) + var/should_message = SPT_PROB(process_shock_message_chance, seconds_per_tick) + + zap(victim, + processing_shock_power_this_tick, + stun = should_stun, + spark = SPT_PROB(spark_chance, seconds_per_tick), + animation = should_stun, message = FALSE, + message = should_stun, + tell_victim_if_no_message = should_message, + ignore_immunity = TRUE, + jitter_time = seconds_per_tick, + stutter_time = 0, + delay_stun = TRUE, + knockdown = TRUE, + ignore_gloves = TRUE + ) + processing_shock_power_this_tick = 0 + +/// If someone is aggrograbbing us and targetting our limb, intensity progress is multiplied against this. +#define LIMB_AGGROGRABBED_PROGRESS_MULT 0.5 + +/// Returns the multiplier used by our intensity progress. Intensity increment is multiplied against this. +/datum/wound/electrical_damage/proc/get_progress_mult() + var/progress_mult = get_base_mult() * seconds_per_intensity_mult + + if (!limb_essential()) + progress_mult *= limb_unimportant_progress_mult + + if (isliving(victim.pulledby)) + var/mob/living/living_puller = victim.pulledby + if (living_puller.grab_state >= GRAB_AGGRESSIVE && living_puller.zone_selected == limb.body_zone) + progress_mult *= LIMB_AGGROGRABBED_PROGRESS_MULT // they're holding it down + + if (victim.stat == DEAD) + progress_mult *= ELECTRICAL_DAMAGE_DEAD_PROGRESS_MULT // doesnt totally stop it but slows it down a lot + + return progress_mult +#undef LIMB_AGGROGRABBED_PROGRESS_MULT + +/// Returns the multiplier used by the damage we deal. +/datum/wound/electrical_damage/proc/get_damage_mult(mob/living/target) + SHOULD_BE_PURE(TRUE) + + var/damage_mult = get_base_mult() + + if (!limb_essential()) + damage_mult *= limb_unimportant_damage_mult + + return damage_mult * ELECTRICAL_DAMAGE_POWER_PER_TICK_MULT + +/// Returns the global multiplier used by both progress and damage. +/datum/wound/electrical_damage/proc/get_base_mult() + var/base_mult = 1 + + if (victim) + if (IS_IN_STASIS(victim)) + base_mult *= ELECTRICAL_DAMAGE_ON_STASIS_MULT + if (victim.body_position == LYING_DOWN) + base_mult *= ELECTRICAL_DAMAGE_LYING_DOWN_MULT + if (limb.grasped_by) + base_mult *= ELECTRICAL_DAMAGE_GRASPED_MULT + + if (victim.has_status_effect(/datum/status_effect/determined)) + base_mult *= WOUND_DETERMINATION_BLEED_MOD + + if (HAS_TRAIT(victim, TRAIT_SHOCKIMMUNE)) // it'd be a bit cheesy to just become immune to this, so it only makes it a lot lot better + base_mult *= shock_immunity_self_damage_reduction + + var/splint_mult = (limb.current_gauze ? limb.current_gauze.splint_factor : 1) + base_mult *= splint_mult + + return overall_effect_mult * base_mult + +/// Is called after seconds_for_intensity is modified by get_progress_mult(). +/datum/wound/electrical_damage/proc/modify_progress_after_progress_mult(seconds_for_intensity, seconds_per_tick) + if (!victim) + return seconds_for_intensity + + return seconds_for_intensity - (get_heat_healing() * seconds_per_tick) + +/// Returns how many deciseconds progress should be reduced by, based on the current heat of our victim's body. +/datum/wound/electrical_damage/proc/get_heat_healing(do_message = prob(heat_heal_message_chance)) + var/healing_amount = max((victim.bodytemperature - heat_thresh_to_heal), 0) * heat_differential_healing_mult + if (do_message && healing_amount) + to_chat(victim, span_notice("You feel the solder within your [limb.plaintext_zone] reform and repair your [name]...")) + + return healing_amount + +/// Changes intensity by the given amount, and then updates our status, removing ourselves if fixed. +/datum/wound/electrical_damage/proc/adjust_intensity(to_adjust) + intensity = clamp((intensity + to_adjust), 0, processing_full_shock_threshold) + + if (disable_at_intensity_mult) + set_disabling(get_intensity_mult() >= disable_at_intensity_mult) + + remove_if_fixed() + +/datum/wound/electrical_damage/wound_injury(datum/wound/electrical_damage/old_wound, attack_direction) + . = ..() + + if (old_wound) + intensity = max(intensity, old_wound.intensity) + processing_shock_power_this_tick = old_wound.processing_shock_power_this_tick + + do_sparks(initial_sparks_amount, FALSE, victim) + +/datum/wound/electrical_damage/modify_desc_before_span(desc, mob/user) + . = ..() + + if (limb.current_gauze) + return + + var/intensity_mult = get_intensity_mult() + if (intensity_mult < 0.2 || (victim.stat == DEAD)) + return + + . += ", and " + + var/extra + switch (intensity_mult) + if (0.2 to 0.4) + extra += "[span_deadsay("is letting out some sparks")]" + if (0.4 to 0.6) + extra += "[span_deadsay("is sparking quite a bit")]" + if (0.6 to 0.8) + extra += "[span_deadsay("is practically hemorrhaging sparks")]" + if (0.8 to 1) + extra += "[span_deadsay("has golden bolts of electricity constantly striking the surface")]" + + . += extra + +/datum/wound/electrical_damage/get_scanner_description(mob/user) + . = ..() + + . += "\nWound status: [get_wound_status_info()]" + +/datum/wound/electrical_damage/get_simple_scanner_description(mob/user) + . = ..() + + . += "\nWound status: [get_wound_status_info()]" + +/// Returns a string with our fault intensity and threshold to removal for use in health analyzers. +/datum/wound/electrical_damage/proc/get_wound_status_info() + return "Fault intensity is currently at [span_bold("[get_intensity_mult() * 100]")]%. It must be reduced to [span_blue("[minimum_intensity]")]% to remove the wound." + +// this wound is unaffected by cryoxadone and pyroxadone +/datum/wound/electrical_damage/on_xadone(power) + return + +/datum/wound/electrical_damage/item_can_treat(obj/item/potential_treater, mob/user) + if (istype(potential_treater, /obj/item/stack/cable_coil) && ((user.pulling == victim && user.grab_state >= GRAB_AGGRESSIVE) || (limb.burn_dam <= ELECTRICAL_DAMAGE_MAX_BURN_DAMAGE_TO_LET_WIRES_REPAIR))) + return TRUE // if we're aggrograbbed, or relatively undamaged, go ahead. else, we dont want to impede normal treatment + + return ..() + +/datum/wound/electrical_damage/treat(obj/item/treating_item, mob/user) + if (treating_item.tool_behaviour == TOOL_WIRECUTTER || treating_item.tool_behaviour == TOOL_RETRACTOR) + return wirecut(treating_item, user) + + if (istype(treating_item, /obj/item/stack/medical/suture) || istype(treating_item, /obj/item/stack/cable_coil)) + return suture_wires(treating_item, user) + + return ..() + +/** + * The "trauma" treatment, done with cables/sutures. Sutures get a debuff. + * Low self-tend penalty. + * Very fast, but low value. Eats up wires for breakfast. + * Has limited wire/HUD bonuses. If you're a robo, use a wirecutter instead. + */ +/datum/wound/electrical_damage/proc/suture_wires(obj/item/stack/suturing_item, mob/living/carbon/human/user) + if (!suturing_item.tool_start_check()) + return TRUE + + var/is_suture = (istype(suturing_item, /obj/item/stack/medical/suture)) + + var/change = (processing_full_shock_threshold * wire_repair_percent) * ELECTRICAL_DAMAGE_SUTURE_WIRE_HEALING_AMOUNT_MULT + var/delay_mult = 1 + if (user == victim) + delay_mult *= 1.5 + if (is_suture) + delay_mult *= 2 + var/obj/item/stack/medical/suture/suture_item = suturing_item + var/obj/item/stack/medical/suture/base_suture = /obj/item/stack/medical/suture + change += (suture_item.heal_brute - initial(base_suture.heal_brute)) + + // as this is the trauma treatment, there are less bonuses + // if youre doing this, youre probably doing this on-the-spot + if (HAS_TRAIT(user, TRAIT_KNOW_ROBO_WIRES)) + delay_mult *= 0.8 + else if (HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES)) + delay_mult *= 0.9 + if (HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD)) + delay_mult *= 0.8 + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + change *= 1.2 + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + var/replacing_or_suturing = (is_suture ? "repairing some" : "replacing") + while (suturing_item.tool_start_check()) + user?.visible_message(span_danger("[user] begins [replacing_or_suturing] wiring within [their_or_other] [limb.plaintext_zone] with [suturing_item]..."), \ + span_notice("You begin [replacing_or_suturing] wiring within [your_or_other] [limb.plaintext_zone] with [suturing_item]...")) + if (!suturing_item.use_tool(target = victim, user = user, delay = ELECTRICAL_DAMAGE_SUTURE_WIRE_BASE_DELAY * delay_mult, amount = 1, volume = 50, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + if (user != victim && user.combat_mode) + user?.visible_message(span_danger("[user] mangles some of [their_or_other] [limb.plaintext_zone]'s wiring!"), \ + span_danger("You mangle some of [your_or_other] [limb.plaintext_zone]'s wiring!"), ignored_mobs = victim) + to_chat(victim, span_userdanger("[capitalize(your_or_other)] mangles some of your [limb.plaintext_zone]'s wiring!")) + adjust_intensity(change * 2) + else + var/repairs_or_replaces = (is_suture ? "repairs" : "replaces") + var/repair_or_replace = (is_suture ? "repair" : "replace") + user?.visible_message(span_notice("[user] [repairs_or_replaces] some of [their_or_other] [limb.plaintext_zone]'s wiring!"), \ + span_notice("You [repair_or_replace] some of [your_or_other] [limb.plaintext_zone]'s wiring!")) + adjust_intensity(-change) + victim.balloon_alert(user, "intensity reduced to [get_intensity_mult() * 100]%") + + if (fixed()) + return TRUE + return TRUE + +/** + * The "proper" treatment, done with wirecutters/retractors. Retractors get a debuff. + * High self-tend penalty. + * Slow, but high value. + * Has high wire/HUD bonuses. The ideal treatment for a robo. + */ +/datum/wound/electrical_damage/proc/wirecut(obj/item/wirecutting_tool, mob/living/carbon/human/user) + if (!wirecutting_tool.tool_start_check()) + return TRUE + + var/is_retractor = (wirecutting_tool.tool_behaviour == TOOL_RETRACTOR) + + var/change = (processing_full_shock_threshold * wirecut_repair_percent) + var/delay_mult = 1 + if (user == victim) + delay_mult *= 2.5 + if (is_retractor) + delay_mult *= 2 + change *= 0.8 + var/knows_wires = FALSE + if (HAS_TRAIT(user, TRAIT_KNOW_ROBO_WIRES)) + delay_mult *= 0.9 + change *= 1.7 + knows_wires = TRUE + else if (HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES)) + change *= 1.35 + knows_wires = TRUE + if (HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD)) + if (knows_wires) + delay_mult *= 0.9 + else + delay_mult *= 0.75 + if (HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + delay_mult *= 0.8 + + var/their_or_other = (user == victim ? "[user.p_their()]" : "[victim]'s") + var/your_or_other = (user == victim ? "your" : "[victim]'s") + while (wirecutting_tool.tool_start_check()) + user?.visible_message(span_danger("[user] begins resetting misplaced wiring within [their_or_other] [limb.plaintext_zone]..."), \ + span_notice("You begin resetting misplaced wiring within [your_or_other] [limb.plaintext_zone]...")) + if (!wirecutting_tool.use_tool(target = victim, user = user, delay = ELECTRICAL_DAMAGE_WIRECUTTER_BASE_DELAY * delay_mult, volume = 50, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + if (user != victim && user.combat_mode) + user?.visible_message(span_danger("[user] mangles some of [their_or_other] [limb.plaintext_zone]'s wiring!"), \ + span_danger("You mangle some of [your_or_other] [limb.plaintext_zone]'s wiring!"), ignored_mobs = victim) + to_chat(victim, span_userdanger("[capitalize(your_or_other)] mangles some of your [limb.plaintext_zone]'s wiring!")) + adjust_intensity(change * 2) + else + user?.visible_message(span_notice("[user] resets some of [their_or_other] [limb.plaintext_zone]'s wiring!"), \ + span_notice("You reset some of [your_or_other] [limb.plaintext_zone]'s wiring!")) + adjust_intensity(-change) + victim.balloon_alert(user, "intensity reduced to [get_intensity_mult() * 100]%") + + if (fixed()) + return TRUE + return TRUE + +/// If fixed() is true, we remove ourselves and return TRUE. FALSE otherwise. +/datum/wound/electrical_damage/proc/remove_if_fixed() + if (fixed()) + to_chat(victim, span_green("Your [limb.plaintext_zone] has recovered from its [name]!")) + remove_wound() + return TRUE + return FALSE + +/// Should we remove ourselves? +/datum/wound/electrical_damage/proc/fixed() + return (intensity <= minimum_intensity || isnull(limb)) + +/// Returns the multiplier we apply to our outgoing damage based off our current intensity. Is always between 0-1. +/datum/wound/electrical_damage/proc/get_intensity_mult() + return (min((intensity / processing_full_shock_threshold), 1)) + +/// Wrapper for electrocute_act +/datum/wound/electrical_damage/proc/zap( + mob/living/target, + damage, + coeff = 1, + stun, + spark = TRUE, + animation = TRUE, + message = TRUE, + ignore_immunity = FALSE, + delay_stun = FALSE, + knockdown = FALSE, + ignore_gloves = FALSE, + tell_victim_if_no_message = TRUE, + jitter_time = 20 SECONDS, + stutter_time = 4 SECONDS, +) + + var/flags = NONE + if (!stun) + flags |= SHOCK_NOSTUN + if (!animation) + flags |= SHOCK_NO_HUMAN_ANIM + if (!message) + flags |= SHOCK_SUPPRESS_MESSAGE + if (tell_victim_if_no_message && target == victim) + to_chat(target, span_warning("Your [limb.plaintext_zone] short-circuits and zaps you!")) + if (ignore_immunity) + flags |= SHOCK_IGNORE_IMMUNITY + if (delay_stun) + flags |= SHOCK_DELAY_STUN + if (knockdown) + flags |= SHOCK_KNOCKDOWN + if (ignore_gloves) + flags |= SHOCK_NOGLOVES + + target.electrocute_act(damage, limb, coeff, flags, jitter_time, stutter_time) + if (spark) + do_sparks(rand(process_shock_spark_count_min, process_shock_spark_count_max), FALSE, victim) + +// Slash +// Fast to rise, but lower damage overall +// Also a bit easy to treat +/datum/wound/electrical_damage/slash + simple_desc = "Wiring has been slashed open, resulting in a fault that quickly intensifies!" + +/datum/wound/electrical_damage/slash/moderate + name = "Frayed Wiring" + desc = "Internal wiring has suffered a slight abrasion, causing a slow electrical fault that will intensify over time." + occur_text = "lets out a few sparks, as a few frayed wires stick out" + examine_desc = "has a few frayed wires sticking out" + treat_text = "Replacing of damaged wiring, though repairs via wirecutting instruments or sutures may suffice, albeit at limited efficiency. In case of emergency, \ + subject may be subjected to high temperatures to allow solder to reset." + + sound_effect = 'modular_skyrat/modules/medical/sound/robotic_slash_T1.ogg' + + severity = WOUND_SEVERITY_MODERATE + + sound_volume = 30 + + threshold_penalty = 20 + + intensity = 10 SECONDS + processing_full_shock_threshold = 3 MINUTES + + processing_shock_power_per_second_max = 0.5 + processing_shock_power_per_second_min = 0.4 + + processing_shock_stun_chance = 0 + processing_shock_spark_chance = 30 + + process_shock_spark_count_max = 1 + process_shock_spark_count_min = 1 + + wirecut_repair_percent = 0.085 // not even faster at this point + wire_repair_percent = 0.035 + + initial_sparks_amount = 1 + + status_effect_type = /datum/status_effect/wound/electrical_damage/slash/moderate + + a_or_from = "from" + + scar_keyword = "slashmoderate" + +/datum/wound_pregen_data/electrical_damage/slash/moderate + abstract = FALSE + wound_path_to_generate = /datum/wound/electrical_damage/slash/moderate + threshold_minimum = 35 + +/datum/wound/electrical_damage/slash/severe + name = "Severed Conduits" + desc = "A number of wires have been completely cut, resulting in electrical faults that will intensify at a worrying rate." + occur_text = "sends some electrical fiber in the direction of the blow, beginning to profusely spark" + examine_desc = "has multiple severed wires visible to the outside" + treat_text = "Containment of damaged wiring via gauze, then application of fresh wiring/sutures, or resetting of displaced wiring via wirecutter/retractor." + + sound_effect = 'modular_skyrat/modules/medical/sound/robotic_slash_T2.ogg' + + severity = WOUND_SEVERITY_SEVERE + + sound_volume = 15 + + threshold_penalty = 30 + + intensity = 10 SECONDS + processing_full_shock_threshold = 2 MINUTES + + processing_shock_power_per_second_max = 0.7 + processing_shock_power_per_second_min = 0.6 + + processing_shock_stun_chance = 0 + processing_shock_spark_chance = 60 + + process_shock_spark_count_max = 2 + process_shock_spark_count_min = 1 + + wirecut_repair_percent = 0.1 + wire_repair_percent = 0.032 + + initial_sparks_amount = 3 + + status_effect_type = /datum/status_effect/wound/electrical_damage/slash/severe + + a_or_from = "from" + + scar_keyword = "slashsevere" + +/datum/wound_pregen_data/electrical_damage/slash/severe + abstract = FALSE + wound_path_to_generate = /datum/wound/electrical_damage/slash/severe + threshold_minimum = 60 + +/datum/wound/electrical_damage/slash/critical + name = "Systemic Fault" + desc = "A significant portion of the power distribution network has been cut open, resulting in massive power loss and runaway electrocution." + occur_text = "lets out a violent \"zhwarp\" sound as angry electric arcs attack the surrounding air" + examine_desc = "has lots of wires mauled wires sticking out" + treat_text = "Immediate securing via gauze, followed by emergency cable replacement and securing via wirecutters or retractor. \ + If the fault has become uncontrollable, extreme heat therapy is recommended." + + severity = WOUND_SEVERITY_CRITICAL + wound_flags = (ACCEPTS_GAUZE|MANGLES_INTERIOR|CAN_BE_GRASPED|SPLINT_OVERLAY) + + sound_effect = 'modular_skyrat/modules/medical/sound/robotic_slash_T3.ogg' + + sound_volume = 30 + + threshold_penalty = 50 + + intensity = 10 SECONDS + processing_full_shock_threshold = 1.25 MINUTES + + processing_shock_power_per_second_max = 1.3 + processing_shock_power_per_second_min = 1.1 + + processing_shock_stun_chance = 5 + processing_shock_spark_chance = 90 + + process_shock_spark_count_max = 3 + process_shock_spark_count_min = 2 + + wirecut_repair_percent = 0.12 + wire_repair_percent = 0.03 + + initial_sparks_amount = 8 + + status_effect_type = /datum/status_effect/wound/electrical_damage/slash/critical + + a_or_from = "a" + + scar_keyword = "slashcritical" + +/datum/wound_pregen_data/electrical_damage/slash/critical + abstract = FALSE + wound_path_to_generate = /datum/wound/electrical_damage/slash/critical + threshold_minimum = 100 + +#undef ELECTRICAL_DAMAGE_ON_STASIS_MULT +#undef ELECTRICAL_DAMAGE_GRASPED_MULT +#undef ELECTRICAL_DAMAGE_LYING_DOWN_MULT +#undef ELECTRICAL_DAMAGE_DEAD_PROGRESS_MULT + +#undef ELECTRICAL_DAMAGE_WIRECUTTER_BASE_DELAY +#undef ELECTRICAL_DAMAGE_SUTURE_WIRE_BASE_DELAY + +#undef ELECTRICAL_DAMAGE_MINIMUM_SHOCK_POWER_PER_ZAP +#undef ELECTRICAL_DAMAGE_MAX_BURN_DAMAGE_TO_LET_WIRES_REPAIR +#undef ELECTRICAL_DAMAGE_POWER_PER_TICK_MULT +#undef ELECTRICAL_DAMAGE_SUTURE_WIRE_HEALING_AMOUNT_MULT diff --git a/modular_skyrat/modules/medical/code/wounds/wound_effects.dm b/modular_skyrat/modules/medical/code/wounds/wound_effects.dm new file mode 100644 index 00000000000..8f9aef16210 --- /dev/null +++ b/modular_skyrat/modules/medical/code/wounds/wound_effects.dm @@ -0,0 +1,33 @@ +/datum/status_effect/wound/blunt/robotic/moderate + id = "unsecure_moderate" + +/datum/status_effect/wound/blunt/robotic/severe + id = "unsecure_severe" + +/datum/status_effect/wound/blunt/robotic/critical + id = "unsecure_critical" + +/datum/status_effect/wound/electrical_damage/slash/moderate + id = "electric_slash_moderate" + +/datum/status_effect/wound/electrical_damage/slash/severe + id = "electric_slash_severe" + +/datum/status_effect/wound/electrical_damage/slash/critical + id = "electric_slash_critical" + +/datum/status_effect/wound/electrical_damage/pierce/moderate + id = "electric_pierce_moderate" + +/datum/status_effect/wound/electrical_damage/pierce/severe + id = "electric_pierce_severe" + +/datum/status_effect/wound/electrical_damage/pierce/critical + id = "electric_pierce_critical" + +/datum/status_effect/wound/burn/robotic/moderate + id = "overheated" +/datum/status_effect/wound/burn/robotic/severe + id = "warpedmetal" +/datum/status_effect/wound/burn/robotic/critical + id = "demagnetizedmetal" diff --git a/modular_skyrat/modules/medical/readme.md b/modular_skyrat/modules/medical/readme.md new file mode 100644 index 00000000000..6ab0af32ddf --- /dev/null +++ b/modular_skyrat/modules/medical/readme.md @@ -0,0 +1,51 @@ + + +https://github.com/Skyrat-SS13/Skyrat-tg/pull/2336 +https://github.com/Skyrat-SS13/Skyrat-tg/pull/23733 + +## Skyrat Medical Update + +Module ID: SKYRAT_MEDICAL_UPDATE + +### Description: + +Various changes to the medical system, from adding bandage overlays, to new wounds, to modularized procs. + + + +### TG Proc/File Changes: + +- code/_DEFINES/wounds.dm: Added muscle/synth wound series, added them to the global list of wound series +- cat2_medicine_reagents.dm: /datum/reagent/medicine/c2/hercuri/on_mob_life, Allowed hercuri to affect synthetics, also changed hercuri process flags for this purpose +- quirks.dm: Commented out the quadruple_amputee/frail blacklist as frail can now apply to prosthetics + + +### Modular Overrides: + +- N/A + + +### Defines: + +- Many local synthetic wound defines + + +### Included files that are not contained in this module: + +- strings/wounds/metal_scar_desc.json -- Required to be here for _string_lists.dm usage + + +### Credits: + +Azarak - Original medical update, muscle wounds, bandage overlays +Niko - Synthetic wounds +TG coding/Skyrat coding channels and community - Support, ideas, reviews + + diff --git a/modular_skyrat/modules/medical/sound/robotic_slash_T1.ogg b/modular_skyrat/modules/medical/sound/robotic_slash_T1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..cb7b3d0a79f49992a1d59f686b5b91c7176f9ae2 GIT binary patch literal 34541 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�>@N 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 x{;Oy6Z0f2#Sm73|UL_hgTVo};BNTRM3?@C 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 literal 0 HcmV?d00001 diff --git a/modular_skyrat/modules/medical/sound/robotic_slash_T3.ogg b/modular_skyrat/modules/medical/sound/robotic_slash_T3.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f0ea8e2217027a04c58ce38f27925175106b34c7 GIT binary patch literal 63099 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 Date: Fri, 6 Oct 2023 23:41:55 +0200 Subject: [PATCH 04/78] Fixes an issue with COMSIG_MOB_ABILITY_STARTED for spells [MDB IGNORE] (#24137) * Fixes an issue with COMSIG_MOB_ABILITY_STARTED for spells (#78775) ## About The Pull Request The signal `COMSIG_MOB_ABILITY_STARTED` is used by both cooldown actions and spells, which are a subtype of cooldown actions. However, cooldown actions pass two values, `src` and the action's target. Spells only passed `src`, even though its the exact same signal. This caused issues with the ability telegraph component, as it has to interrupt the casting of spells and actions to add a delay, and most if not all spells won't work without the target being passed with the signal. Closes #78715. ## Why It's Good For The Game It caused bugs and is just bad design in general. ## Changelog :cl: fix: Ice whelps can now use spells given to them by admins, and people who have polymorphed into ice whelps can now polymorph back to normal. /:cl: * Fixes an issue with COMSIG_MOB_ABILITY_STARTED for spells --------- Co-authored-by: GPeckman <21979502+GPeckman@users.noreply.github.com> --- code/modules/spells/spell.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index f03cd4927f8..5bc39b389b3 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -140,7 +140,7 @@ // Where the cast chain starts /datum/action/cooldown/spell/PreActivate(atom/target) - if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START) + if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src, target) & COMPONENT_BLOCK_ABILITY_START) return FALSE if(target == owner) target = get_caster_from_target(target) From c9d8dea5f7464b41e4b2b7a621c50b8ba41fca35 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:42:50 +0200 Subject: [PATCH 05/78] Moves silicon camera (photos, not mob cameras) out of click code [MDB IGNORE] (#24138) * Moves silicon camera (photos, not mob cameras) out of click code (#78774) ## About The Pull Request Atomized from the swing branch. Moves silicon camera (taking photos, not mob camera stuff) out of their core click code. It now uses click intercepts. (There's an argument to be made to use signals rather than click intercept as it's rather antiquated but w/e.) - [x] I tested this PR ## Why It's Good For The Game Makes it easier to unifiy click a bit more in the future. Reduces surface area of a feature. ## Changelog :cl: Melbert qol: AI, cyborg, and PAI camera (photo taking) behavior now uses balloon alerts and has sound effects associated refactor: Refactored AI, cyborg, and PAI camera (photo taking) code fix: fixed being unable to print photos as a cyborg when below 50% toner, even though photos only take 5% /:cl: * Moves silicon camera (photos, not mob cameras) out of click code --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/_onclick/ai.dm | 4 - code/_onclick/cyborg.dm | 5 - code/modules/pai/camera.dm | 9 +- code/modules/pai/hud.dm | 3 +- .../photography/camera/silicon_camera.dm | 116 ++++++++++++------ 5 files changed, 83 insertions(+), 54 deletions(-) diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 95697862b3c..3d632a99f73 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -73,10 +73,6 @@ if(world.time <= next_move) return - if(aicamera.in_camera_mode) - aicamera.toggle_camera_mode(sound = FALSE) - aicamera.captureimage(pixel_turf, usr) - return if(waypoint_mode) waypoint_mode = 0 set_waypoint(A) diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index 92340c59bc9..8cc8603f552 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -48,11 +48,6 @@ face_atom(A) // change direction to face what you clicked on - if(aicamera.in_camera_mode) //Cyborg picture taking - aicamera.toggle_camera_mode(sound = FALSE) - aicamera.captureimage(A, usr) - return - var/obj/item/W = get_active_held_item() if(!W && get_dist(src,A) <= interaction_range) diff --git a/code/modules/pai/camera.dm b/code/modules/pai/camera.dm index a091b208638..319f20e3699 100644 --- a/code/modules/pai/camera.dm +++ b/code/modules/pai/camera.dm @@ -1,10 +1,3 @@ -/mob/living/silicon/pai/ClickOn(atom/target, params) - . = ..() - if(aicamera && aicamera.in_camera_mode) - aicamera.toggle_camera_mode(sound = FALSE) - aicamera.captureimage(target, usr) - return TRUE - /obj/item/camera/siliconcam/pai_camera name = "pAI photo camera" light_color = COLOR_PAI_GREEN @@ -13,7 +6,7 @@ var/number = length(stored) picture.picture_name = "Image [number] (taken by [loc.name])" stored[picture] = TRUE - playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, TRUE, -3) + playsound(src, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, TRUE, -3) balloon_alert(user, "image recorded") /** diff --git a/code/modules/pai/hud.dm b/code/modules/pai/hud.dm index 530780758d2..523d57d17b3 100644 --- a/code/modules/pai/hud.dm +++ b/code/modules/pai/hud.dm @@ -147,7 +147,8 @@ required_software = "Photography Module" /atom/movable/screen/pai/image_take/Click() - if(!..()) + . = ..() + if(!.) return var/mob/living/silicon/pai/pAI = usr pAI.aicamera.toggle_camera_mode(usr) diff --git a/code/modules/photography/camera/silicon_camera.dm b/code/modules/photography/camera/silicon_camera.dm index 836ecc2690a..9cdbee1bc2b 100644 --- a/code/modules/photography/camera/silicon_camera.dm +++ b/code/modules/photography/camera/silicon_camera.dm @@ -1,23 +1,47 @@ /obj/item/camera/siliconcam name = "silicon photo camera" - var/in_camera_mode = FALSE + resistance_flags = INDESTRUCTIBLE + /// List of all pictures taken by this camera. var/list/datum/picture/stored = list() -/obj/item/camera/siliconcam/ai_camera - name = "AI photo camera" - flash_enabled = FALSE +/// Checks if we can take a picture at this moment. Returns TRUE if we can, FALSE if we can't. +/obj/item/camera/siliconcam/proc/can_take_picture(mob/living/silicon/clicker) + if(clicker.stat != CONSCIOUS || clicker.incapacitated()) + return FALSE + return TRUE + +/obj/item/camera/siliconcam/proc/InterceptClickOn(mob/living/silicon/clicker, params, atom/clicked_on) + if(!can_take_picture(clicker)) + return + clicker.face_atom(clicked_on) + captureimage(clicked_on, clicker) + toggle_camera_mode(clicker, sound = FALSE) +/// Toggles the camera mode on or off. +/// If sound is TRUE, plays a sound effect and displays a message on successful toggle /obj/item/camera/siliconcam/proc/toggle_camera_mode(mob/user, sound = TRUE) - in_camera_mode = !in_camera_mode + if(user.click_intercept == src) + user.click_intercept = null + + else if(isnull(user.click_intercept)) + user.click_intercept = src + + else + // Trying to turn on camera mode while you have another click intercept active, such as malf abilities + if(sound) + balloon_alert(user, "can't enable camera mode!") + playsound(user, 'sound/machines/buzz-sigh.ogg', 25, TRUE) + return + if(sound) - playsound(src, 'sound/items/wirecutter.ogg', 50, TRUE) - to_chat(user, span_notice("Camera mode: [in_camera_mode ? "Activated" : "Deactivated"].")) + playsound(user, 'sound/items/wirecutter.ogg', 50, TRUE) + balloon_alert(user, "camera mode [user.click_intercept == src ? "activated" : "deactivated"]") /obj/item/camera/siliconcam/proc/selectpicture(mob/user) RETURN_TYPE(/datum/picture) if(!length(stored)) - to_chat(user, span_notice("ERROR: No stored photos located.")) + user.balloon_alert(user, "no stored photos!") return var/list/nametemp = list() var/list/temp = list() @@ -25,9 +49,7 @@ nametemp += stored_photo.picture_name temp[stored_photo.picture_name] = stored_photo var/find = tgui_input_list(user, "Select image", "Storage", nametemp) - if(isnull(find)) - return - if(isnull(temp[find])) + if(isnull(find) || isnull(temp[find])) return return temp[find] @@ -36,48 +58,70 @@ if(istype(selection)) show_picture(user, selection) +/obj/item/camera/siliconcam/ai_camera + name = "AI photo camera" + flash_enabled = FALSE + +/obj/item/camera/siliconcam/ai_camera/can_take_picture(mob/living/silicon/ai/clicker) + if(clicker.control_disabled) + return FALSE + return ..() + +/obj/item/camera/siliconcam/ai_camera/balloon_alert(mob/viewer, text) + if(isAI(loc)) + // redirects balloon alerts on us to balloon alerts on our ai eye + var/mob/living/silicon/ai/ai = loc + return ai.eyeobj.balloon_alert(viewer, text) + + return ..() + /obj/item/camera/siliconcam/ai_camera/after_picture(mob/user, datum/picture/picture) var/number = length(stored) picture.picture_name = "Image [number] (taken by [loc.name])" stored[picture] = TRUE - to_chat(user, span_notice("Image recorded.")) + balloon_alert(user, "image recorded") + user.playsound_local(get_turf(user), pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, TRUE, -3) /obj/item/camera/siliconcam/robot_camera name = "Cyborg photo camera" var/printcost = 2 -/obj/item/camera/siliconcam/robot_camera/after_picture(mob/user, datum/picture/picture) - var/mob/living/silicon/robot/C = loc - if(istype(C) && istype(C.connected_ai)) - var/number = C.connected_ai.aicamera.stored.len +/obj/item/camera/siliconcam/robot_camera/can_take_picture(mob/living/silicon/robot/clicker) + if(clicker.lockcharge) + return FALSE + return ..() + +/obj/item/camera/siliconcam/robot_camera/after_picture(mob/living/silicon/robot/user, datum/picture/picture) + if(istype(user) && istype(user.connected_ai)) + var/number = user.connected_ai.aicamera.stored.len picture.picture_name = "Image [number] (taken by [loc.name])" - C.connected_ai.aicamera.stored[picture] = TRUE - to_chat(usr, span_notice("Image recorded and saved to remote database.")) + user.connected_ai.aicamera.stored[picture] = TRUE + balloon_alert(user, "image recorded and uploaded") else var/number = stored.len picture.picture_name = "Image [number] (taken by [loc.name])" stored[picture] = TRUE - to_chat(usr, span_notice("Image recorded and saved to local storage. Upload will happen automatically if unit is lawsynced.")) + balloon_alert(user, "image recorded and saved locally") + playsound(src, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, TRUE, -3) -/obj/item/camera/siliconcam/robot_camera/selectpicture(mob/user) - var/mob/living/silicon/robot/R = loc - if(istype(R) && R.connected_ai) - R.picturesync() - return R.connected_ai.aicamera.selectpicture(user) - else - return ..() +/obj/item/camera/siliconcam/robot_camera/selectpicture(mob/living/silicon/robot/user) + if(istype(user) && user.connected_ai) + user.picturesync() + return user.connected_ai.aicamera.selectpicture(user) + return ..() -/obj/item/camera/siliconcam/robot_camera/proc/borgprint(mob/user) - var/mob/living/silicon/robot/C = loc - if(!istype(C) || C.toner < 20) - to_chat(user, span_warning("Insufficent toner to print image.")) +/obj/item/camera/siliconcam/robot_camera/proc/borgprint(mob/living/silicon/robot/user) + if(!istype(user) || user.toner < printcost) + balloon_alert(user, "not enough toner!") return var/datum/picture/selection = selectpicture(user) if(!istype(selection)) - to_chat(user, span_warning("Invalid Image.")) + balloon_alert(user, "invalid image!") return - var/obj/item/photo/p = new /obj/item/photo(C.loc, selection) - p.pixel_x = p.base_pixel_x + rand(-10, 10) - p.pixel_y = p.base_pixel_y + rand(-10, 10) - C.toner -= printcost //All fun allowed. - user.visible_message(span_notice("[C.name] spits out a photograph from a narrow slot on its chassis."), span_notice("You print a photograph.")) + var/obj/item/photo/printed = new(user.drop_location(), selection) + printed.pixel_x = printed.base_pixel_x + rand(-10, 10) + printed.pixel_y = printed.base_pixel_y + rand(-10, 10) + user.toner -= printcost //All fun allowed. + user.visible_message(span_notice("[user.name] spits out a photograph from a narrow slot on its chassis."), span_notice("You print a photograph.")) + balloon_alert(user, "photograph printed") + playsound(src, 'sound/items/taperecorder/taperecorder_print.ogg', 50, TRUE, -3) From 77e72af08b38bb619bdf66e00be3e6931a8e3245 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:43:09 +0200 Subject: [PATCH 06/78] Fixes the HEAL_NEGATIVE_MUTATIONS revive flag [MDB IGNORE] (#24139) * Fixes the HEAL_NEGATIVE_MUTATIONS revive flag (#78768) ## About The Pull Request There is a flag for the revive proc called `HEAL_NEGATIVE_MUTATIONS`. It is supposed to make the revive heal negative and minor negative (e.g. chav, medieval) mutations, but not positive mutations. However, it was bugged and wouldn't heal any mutations. This PR just fixes it. Closes #43547. ## Why It's Good For The Game Its a bugfix. ## Changelog :cl: fix: Adminheal will now properly clear negative mutations as intended. /:cl: * Fixes the HEAL_NEGATIVE_MUTATIONS revive flag --------- Co-authored-by: GPeckman <21979502+GPeckman@users.noreply.github.com> --- code/modules/mob/living/carbon/human/human.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 680e5c14f7e..0905a4ac40f 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -733,7 +733,7 @@ if(heal_flags & HEAL_NEGATIVE_MUTATIONS) for(var/datum/mutation/human/existing_mutation in dna.mutations) if(existing_mutation.quality != POSITIVE) - dna.remove_mutation(existing_mutation.name) + dna.remove_mutation(existing_mutation) if(heal_flags & HEAL_TEMP) set_coretemperature(get_body_temp_normal(apply_change = FALSE)) From 93de4400782a472aafbe114e5bde667211d3566a Mon Sep 17 00:00:00 2001 From: lessthanthree <83487515+lessthnthree@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:43:28 -0700 Subject: [PATCH 07/78] Void Raptor shutters/firelocks (#24131) shutters/firelocks --- _maps/map_files/VoidRaptor/VoidRaptor.dmm | 65 +++++++++++++---------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/_maps/map_files/VoidRaptor/VoidRaptor.dmm b/_maps/map_files/VoidRaptor/VoidRaptor.dmm index 143300c080a..592813db7d5 100644 --- a/_maps/map_files/VoidRaptor/VoidRaptor.dmm +++ b/_maps/map_files/VoidRaptor/VoidRaptor.dmm @@ -1873,7 +1873,6 @@ "aAN" = ( /obj/effect/spawner/structure/window/reinforced, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 8; id = "chemistry_shutters"; name = "Pharmacy Shutters" }, @@ -3484,9 +3483,9 @@ pixel_y = 16 }, /obj/machinery/door/poddoor/shutters/window/preopen{ - dir = 4; id = "paramed_dispatch_desk"; - name = "Desk Shutters" + name = "Desk Shutters"; + dir = 8 }, /obj/item/paper_bin{ pixel_y = 4 @@ -11920,7 +11919,6 @@ "dAP" = ( /obj/effect/spawner/structure/window/reinforced, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 4; id = "genetics_shutters"; name = "Genetics Shutters" }, @@ -13944,7 +13942,6 @@ /obj/item/reagent_containers/syringe/epinephrine, /obj/effect/turf_decal/tile/yellow/fourcorners, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 8; id = "chemistry_shutters"; name = "Pharmacy Shutters" }, @@ -18259,9 +18256,9 @@ req_access = list("medical") }, /obj/machinery/door/poddoor/shutters/window/preopen{ - dir = 4; id = "paramed_dispatch_desk"; - name = "Desk Shutters" + name = "Desk Shutters"; + dir = 8 }, /turf/open/floor/iron/white/smooth_large, /area/station/medical/medbay/lobby) @@ -24383,7 +24380,6 @@ /area/station/engineering/atmos) "haE" = ( /obj/machinery/door/poddoor/shutters/preopen{ - dir = 8; id = "hop"; name = "Privacy Shutters" }, @@ -29278,7 +29274,8 @@ /obj/effect/turf_decal/caution/stand_clear, /obj/machinery/door/poddoor/shutters{ id = "mechbay"; - name = "Mech Bay Shutters" + name = "Mech Bay Shutters"; + dir = 4 }, /obj/effect/turf_decal/stripes/line{ dir = 8 @@ -36933,7 +36930,6 @@ req_access = list("pharmacy") }, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 4; id = "chemistry_shutters"; name = "Pharmacy Shutters" }, @@ -39446,7 +39442,6 @@ /area/station/hallway/primary/fore) "lfY" = ( /obj/machinery/door/poddoor/shutters/preopen{ - dir = 8; id = "hopqueue"; name = "HoP Queue Shutters" }, @@ -40600,7 +40595,6 @@ pixel_y = 9 }, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 4; id = "genetics_shutters"; name = "Genetics Shutters" }, @@ -44465,9 +44459,9 @@ /obj/item/stack/medical/mesh, /obj/item/stack/medical/suture, /obj/machinery/door/poddoor/shutters/window/preopen{ - dir = 4; id = "paramed_dispatch_desk"; - name = "Desk Shutters" + name = "Desk Shutters"; + dir = 8 }, /turf/open/floor/iron/white/smooth_large, /area/station/medical/medbay/lobby) @@ -50365,6 +50359,11 @@ "ogb" = ( /obj/machinery/smartfridge, /obj/effect/turf_decal/bot, +/obj/machinery/door/firedoor, +/obj/machinery/door/poddoor/shutters/preopen{ + id = "kitchen_service"; + name = "Service Shutter" + }, /turf/open/floor/iron/large, /area/station/service/kitchen) "ogc" = ( @@ -50697,7 +50696,8 @@ }, /obj/machinery/door/poddoor/shutters/preopen{ id = "roboticsprivacy"; - name = "Robotics Shutters" + name = "Robotics Shutters"; + dir = 4 }, /turf/open/floor/iron/dark/smooth_large, /area/station/science/robotics/lab) @@ -55225,9 +55225,9 @@ req_access = list("medical") }, /obj/machinery/door/poddoor/shutters/window/preopen{ - dir = 4; id = "paramed_dispatch_desk"; - name = "Desk Shutters" + name = "Desk Shutters"; + dir = 8 }, /obj/item/storage/box/bandages{ pixel_y = 15 @@ -58109,7 +58109,6 @@ req_access = list("hop") }, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 8; id = "hop"; name = "Privacy Shutters" }, @@ -58318,7 +58317,6 @@ }, /obj/machinery/door/firedoor, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 4; id = "rndlab1"; name = "Research and Development Shutter" }, @@ -59608,7 +59606,6 @@ "qDW" = ( /obj/effect/spawner/structure/window/reinforced, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 4; id = "rndlab1"; name = "Research and Development Shutter" }, @@ -61138,6 +61135,11 @@ /area/station/maintenance/aft/upper) "qZl" = ( /obj/machinery/smartfridge/chemistry/preloaded, +/obj/machinery/door/firedoor, +/obj/machinery/door/poddoor/shutters/preopen{ + id = "chemistry_shutters"; + name = "Pharmacy Shutters" + }, /turf/open/floor/iron/freezer, /area/station/medical/pharmacy) "qZz" = ( @@ -64224,7 +64226,6 @@ /area/station/service/cafeteria) "rUf" = ( /obj/machinery/door/poddoor/shutters/preopen{ - dir = 1; id = "chemistry_shutters"; name = "Pharmacy Shutters" }, @@ -66444,7 +66445,6 @@ req_access = list("pharmacy") }, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 1; id = "chemistry_shutters"; name = "Pharmacy Shutters" }, @@ -69158,7 +69158,8 @@ /obj/effect/spawner/structure/window/reinforced, /obj/machinery/door/poddoor/shutters/preopen{ id = "roboticsprivacy"; - name = "Robotics Shutters" + name = "Robotics Shutters"; + dir = 8 }, /turf/open/floor/plating/airless, /area/station/science/robotics/lab) @@ -76768,7 +76769,8 @@ /obj/effect/turf_decal/caution/stand_clear, /obj/machinery/door/poddoor/shutters{ id = "mechbay"; - name = "Mech Bay Shutters" + name = "Mech Bay Shutters"; + dir = 4 }, /obj/machinery/button/door/directional/north{ id = "mechbay"; @@ -83926,6 +83928,11 @@ /area/station/security/checkpoint/engineering) "xnP" = ( /obj/machinery/smartfridge/chemistry/preloaded, +/obj/machinery/door/firedoor, +/obj/machinery/door/poddoor/shutters/preopen{ + id = "chemistry_shutters"; + name = "Pharmacy Shutters" + }, /turf/open/floor/iron/dark/smooth_large, /area/station/medical/pharmacy) "xnR" = ( @@ -87204,13 +87211,13 @@ /turf/open/floor/iron/smooth_large, /area/station/engineering/atmos) "ykT" = ( -/obj/effect/spawner/structure/window/reinforced, +/obj/machinery/smartfridge/chemistry/preloaded, /obj/machinery/door/poddoor/shutters/preopen{ - dir = 1; id = "chemistry_shutters"; name = "Pharmacy Shutters" }, -/turf/open/floor/plating/airless, +/obj/machinery/door/firedoor, +/turf/open/floor/iron/dark/smooth_large, /area/station/medical/pharmacy) "ylg" = ( /obj/effect/turf_decal/trimline/yellow/filled/line{ @@ -119292,7 +119299,7 @@ rFl xgy uQf jLi -ykT +aAN gut uMt sLk @@ -119549,7 +119556,7 @@ gLN dRX jQm tJU -xnP +ykT vVO twU wwr From 7d46eb16dd9426d8c50834d4a6edfdc8478f00f4 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:43:47 +0200 Subject: [PATCH 08/78] Basic Mob Flesh Worm [MDB IGNORE] (#24122) * Basic Mob Flesh Worm (#78744) ## About The Pull Request Fixes #68614 Converts the Flesh Worm (Armsy) into a Basic Mob. Most of its behaviour has been moved into a component which we can use to make arbitrary mobs into linked lists of mobs. To accomplish this I added a signal which is sent when you call any `adjustXLoss` proc, let me know if my implementation is "calling the same signal from several places" by a backdoor, I wanted to avoid registering to 6 signals but I'll change it if I must. While I was here I killed the unused "lesser" variant because we stopped using it. Resultingly, Ascended Armsy doesn't need to distinguish itself by inflating the sprite, so it doesn't. This means that now flesh worms are using their sprites as intended to be displayed, but if people really miss all of its segments being poorly scaled by the byond engine then I guess I can restore it. ## Why It's Good For The Game ![dreamseeker_p8vOpZGXII](https://github.com/tgstation/tgstation/assets/7483112/3389d3a9-16cd-4e1e-938e-dfa18d0da0af) ## Changelog :cl: refactor: Flesh Worms are now basic mobs. Please report any unexpected behaviour. sprite: Flesh Worms are a little bit slimmer. /:cl: * Basic Mob Flesh Worm --------- Co-authored-by: Jacquerel --- code/__DEFINES/combat.dm | 2 + .../signals/signals_mob/signals_mob_carbon.dm | 3 + .../signals/signals_mob/signals_mob_living.dm | 11 + code/datums/components/cult_ritual_item.dm | 2 +- code/datums/components/mob_chain.dm | 205 ++++++++++++++++ code/datums/components/rot.dm | 2 +- code/datums/elements/amputating_limbs.dm | 14 +- code/datums/wounds/_wounds.dm | 2 +- .../game/objects/items/devices/polycircuit.dm | 2 +- code/modules/admin/admin_verbs.dm | 52 +++- .../antagonists/cult/cult_bastard_sword.dm | 2 +- code/modules/antagonists/cult/cult_items.dm | 2 +- code/modules/antagonists/cult/runes.dm | 2 +- .../heretic/magic/flesh_ascension.dm | 27 +-- .../heretic/structures/knock_final.dm | 3 +- code/modules/clothing/shoes/_shoes.dm | 2 +- .../mob/living/basic/health_adjustment.dm | 35 ++- .../mob/living/basic/heretic/flesh_worm.dm | 137 +++++++++++ .../basic/lavaland/lobstrosity/lobstrosity.dm | 4 +- .../modules/mob/living/carbon/damage_procs.dm | 14 +- code/modules/mob/living/damage_procs.dm | 55 +++-- .../mob/living/simple_animal/damage_procs.dm | 12 + .../simple_animal/hostile/heretic_monsters.dm | 226 ------------------ code/modules/pai/defense.dm | 8 +- code/modules/surgery/bodyparts/_bodyparts.dm | 12 +- code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/mob_chains.dm | 31 +++ .../unit_tests/simple_animal_freeze.dm | 2 - tgstation.dme | 2 + 29 files changed, 563 insertions(+), 309 deletions(-) create mode 100644 code/datums/components/mob_chain.dm create mode 100644 code/modules/mob/living/basic/heretic/flesh_worm.dm create mode 100644 code/modules/unit_tests/mob_chains.dm diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index caff9c319e2..cc9412208fb 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -274,6 +274,8 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define BODY_ZONE_L_LEG "l_leg" #define BODY_ZONE_R_LEG "r_leg" +GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + #define BODY_ZONE_PRECISE_EYES "eyes" #define BODY_ZONE_PRECISE_MOUTH "mouth" #define BODY_ZONE_PRECISE_GROIN "groin" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index c96d21b7163..ad0e6e359b1 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -36,6 +36,9 @@ #define COMSIG_BODYPART_ATTACHED "bodypart_removed" ///from base of /obj/item/bodypart/proc/try_attach_limb(): (new_limb, special) #define COMSIG_CARBON_POST_ATTACH_LIMB "carbon_post_attach_limb" +///from /obj/item/bodypart/proc/receive_damage, sent from the limb owner (limb, brute, burn) +#define COMSIG_CARBON_LIMB_DAMAGED "carbon_limb_damaged" + #define COMPONENT_PREVENT_LIMB_DAMAGE (1 << 0) /// from /obj/item/bodypart/proc/apply_gauze(/obj/item/stack/gauze): (/obj/item/stack/medical/gauze/applied_gauze, /obj/item/stack/medical/gauze/stack_used) #define COMSIG_BODYPART_GAUZED "bodypart_gauzed" /// from /obj/item/stack/medical/gauze/Destroy(): (/obj/item/stack/medical/gauze/removed_gauze) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 273b9f216c7..f697566671e 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -55,6 +55,10 @@ ///from base of element/bane/activate(): (item/weapon, mob/user) #define COMSIG_OBJECT_ON_BANING "obj_on_baning" +///from base of mob/living/on_damage_adjustment +#define COMSIG_LIVING_ADJUST_DAMAGE "living_adjust_damage" + #define COMPONENT_IGNORE_CHANGE (1<<0) + /// from base of mob/living/updatehealth() #define COMSIG_LIVING_HEALTH_UPDATE "living_health_update" ///from base of mob/living/death(): (gibbed) @@ -179,3 +183,10 @@ /// From /datum/ai/behavior/climb_tree/perform() : (mob/living/basic/living_pawn) #define COMSIG_LIVING_CLIMB_TREE "living_climb_tree" + +/// Sent on a mob from /datum/component/mob_chain when component is attached with it as the "front" : (mob/living/basic/tail) +#define COMSIG_MOB_GAINED_CHAIN_TAIL "living_gained_chain_tail" +/// Sent on a mob from /datum/component/mob_chain when component is detached from it as the "front" : (mob/living/basic/tail) +#define COMSIG_MOB_LOST_CHAIN_TAIL "living_detached_chain_tail" +/// Sent from a 'contract chain' button on a mob chain +#define COMSIG_MOB_CHAIN_CONTRACT "living_chain_contracted" diff --git a/code/datums/components/cult_ritual_item.dm b/code/datums/components/cult_ritual_item.dm index 561789d1b6d..13d1ab8d921 100644 --- a/code/datums/components/cult_ritual_item.dm +++ b/code/datums/components/cult_ritual_item.dm @@ -304,7 +304,7 @@ ) if(cultist.blood_volume) - cultist.apply_damage(initial(rune_to_scribe.scribe_damage), BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM), wound_bonus = CANT_WOUND) // *cuts arm* *bone explodes* ever have one of those days? + cultist.apply_damage(initial(rune_to_scribe.scribe_damage), BRUTE, pick(GLOB.arm_zones), wound_bonus = CANT_WOUND) // *cuts arm* *bone explodes* ever have one of those days? var/scribe_mod = initial(rune_to_scribe.scribe_delay) if(!initial(rune_to_scribe.no_scribe_boost) && (our_turf.type in turfs_that_boost_us)) diff --git a/code/datums/components/mob_chain.dm b/code/datums/components/mob_chain.dm new file mode 100644 index 00000000000..a2b16a849a5 --- /dev/null +++ b/code/datums/components/mob_chain.dm @@ -0,0 +1,205 @@ +/** + * Component allowing you to create a linked list of mobs. + * These mobs will follow each other and attack as one, as well as sharing damage taken. + */ +/datum/component/mob_chain + + /// If true then damage we take is passed backwards along the line + var/pass_damage_back + /// If true then we will set our icon state based on line position + var/vary_icon_state + + /// Mob in front of us in the chain + var/mob/living/front + /// Mob behind us in the chain + var/mob/living/back + +/datum/component/mob_chain/Initialize(mob/living/front, pass_damage_back = TRUE, vary_icon_state = FALSE) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + src.front = front + src.pass_damage_back = pass_damage_back + src.vary_icon_state = vary_icon_state + if (!isnull(front)) + SEND_SIGNAL(front, COMSIG_MOB_GAINED_CHAIN_TAIL, parent) + parent.AddComponent(/datum/component/leash, owner = front, distance = 1) // Handles catching up gracefully + +/datum/component/mob_chain/Destroy(force, silent) + if (!isnull(front)) + SEND_SIGNAL(front, COMSIG_MOB_LOST_CHAIN_TAIL, parent) + front = null + back = null + return ..() + +/datum/component/mob_chain/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOB_GAINED_CHAIN_TAIL, PROC_REF(on_gained_tail)) + RegisterSignal(parent, COMSIG_MOB_LOST_CHAIN_TAIL, PROC_REF(on_lost_tail)) + RegisterSignal(parent, COMSIG_MOB_CHAIN_CONTRACT, PROC_REF(on_contracted)) + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_deletion)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + RegisterSignal(parent, COMSIG_ATOM_CAN_BE_PULLED, PROC_REF(on_pulled)) + RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, COMSIG_MOB_ATTACK_RANGED), PROC_REF(on_attack)) + if (vary_icon_state) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_update_icon_state)) + update_mob_appearance() + if (pass_damage_back) + RegisterSignal(parent, COMSIG_LIVING_ADJUST_DAMAGE, PROC_REF(on_adjust_damage)) + RegisterSignal(parent, COMSIG_CARBON_LIMB_DAMAGED, PROC_REF(on_limb_damage)) + + var/datum/action/cooldown/worm_contract/shrink = new(parent) + shrink.Grant(parent) + +/datum/component/mob_chain/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_ATOM_CAN_BE_PULLED, + COMSIG_ATOM_UPDATE_ICON_STATE, + COMSIG_CARBON_LIMB_DAMAGED, + COMSIG_HUMAN_EARLY_UNARMED_ATTACK, + COMSIG_LIVING_ADJUST_DAMAGE, + COMSIG_LIVING_DEATH, + COMSIG_LIVING_UNARMED_ATTACK, + COMSIG_MOB_ATTACK_RANGED, + COMSIG_MOB_CHAIN_CONTRACT, + COMSIG_MOB_GAINED_CHAIN_TAIL, + COMSIG_MOB_LOST_CHAIN_TAIL, + COMSIG_MOVABLE_MOVED, + COMSIG_QDELETING, + )) + qdel(parent.GetComponent(/datum/component/leash)) + var/mob/living/living_parent = parent + var/datum/action/cooldown/worm_contract/shrink = locate() in living_parent.actions + qdel(shrink) + +/// Update how we look +/datum/component/mob_chain/proc/update_mob_appearance() + if (!vary_icon_state) + return + var/mob/living/body = parent + body.update_appearance(UPDATE_ICON_STATE) + +/// Called when something sets us as IT'S front +/datum/component/mob_chain/proc/on_gained_tail(mob/living/body, mob/living/tail) + SIGNAL_HANDLER + back = tail + update_mob_appearance() + +/// Called when our tail loses its chain component +/datum/component/mob_chain/proc/on_lost_tail() + SIGNAL_HANDLER + back = null + update_mob_appearance() + +/// Called when our tail gets pulled up to our body +/datum/component/mob_chain/proc/on_contracted(mob/living/shrinking) + SIGNAL_HANDLER + if (isnull(back)) + return + back.forceMove(shrinking.loc) + var/datum/action/cooldown/worm_contract/shrink = locate() in back.actions + if (isnull(shrink)) + return + INVOKE_ASYNC(shrink, TYPE_PROC_REF(/datum/action, Trigger)) + +/// If we die so does the guy behind us, then stop following the leader +/datum/component/mob_chain/proc/on_death() + SIGNAL_HANDLER + back?.death() + qdel(src) + +/// If we get deleted so does the guy behind us +/datum/component/mob_chain/proc/on_deletion() + SIGNAL_HANDLER + QDEL_NULL(back) + front?.update_appearance(UPDATE_ICON) + +/// Pull our tail behind us when we move +/datum/component/mob_chain/proc/on_moved(mob/living/mover, turf/old_loc) + SIGNAL_HANDLER + if(isnull(back) || back.loc == old_loc) + return + back.Move(old_loc) + +/// Update our visuals based on if we have someone in front and behind +/datum/component/mob_chain/proc/on_update_icon_state(mob/living/our_mob) + SIGNAL_HANDLER + var/current_icon_state = our_mob.base_icon_state + if(isnull(front)) + current_icon_state = "[current_icon_state]_start" + else if(isnull(back)) + current_icon_state = "[current_icon_state]_end" + else + current_icon_state = "[current_icon_state]_mid" + + our_mob.icon_state = current_icon_state + if (isanimal_or_basicmob(our_mob)) + var/mob/living/basic/basic_parent = our_mob + basic_parent.icon_living = current_icon_state + +/// Do not allow someone to be pulled out of the chain +/datum/component/mob_chain/proc/on_pulled(mob/living/our_mob) + SIGNAL_HANDLER + if (!isnull(front)) + return COMSIG_ATOM_CANT_PULL + +/// Tell our tail to attack too +/datum/component/mob_chain/proc/on_attack(mob/living/our_mob, atom/target) + SIGNAL_HANDLER + if (target == back || target == front) + return COMPONENT_CANCEL_ATTACK_CHAIN + if (isnull(back) || QDELETED(target)) + return + INVOKE_ASYNC(back, TYPE_PROC_REF(/mob, ClickOn), target) + +/// On damage or heal, affect our furthest segment +/datum/component/mob_chain/proc/on_adjust_damage(mob/living/our_mob, type, amount, forced) + SIGNAL_HANDLER + if (isnull(back) || forced) + return + if (type == STAMINA) + back.adjustStaminaLoss(amount, forced = forced) + return // Pass stamina changes all the way along so we maintain consistent speed + switch (type) + if(BRUTE) + back.adjustBruteLoss(amount, forced = forced) + if(BURN) + back.adjustFireLoss(amount, forced = forced) + if(TOX) + back.adjustToxLoss(amount, forced = forced) + if(OXY) // If all segments are suffocating we pile damage backwards until our ass starts dying forwards + back.adjustOxyLoss(amount, forced = forced) + if(CLONE) + back.adjustCloneLoss(amount, forced = forced) + return COMPONENT_IGNORE_CHANGE + +/// Special handling for if damage is delegated to a mob's limbs instead of its overall damage +/datum/component/mob_chain/proc/on_limb_damage(mob/living/our_mob, limb, brute, burn) + SIGNAL_HANDLER + if (isnull(back)) + return + if (brute != 0) + back.adjustBruteLoss(brute, updating_health = FALSE) + if (burn != 0) + back.adjustFireLoss(burn, updating_health = FALSE) + if (brute != 0 || burn != 0) + back.updatehealth() + return COMPONENT_PREVENT_LIMB_DAMAGE + +/** + * Shrink the chain of mobs into one tile. + */ +/datum/action/cooldown/worm_contract + name = "Force Contract" + desc = "Forces your body to contract onto a single tile." + background_icon_state = "bg_heretic" + overlay_icon_state = "bg_heretic_border" + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "worm_contract" + cooldown_time = 30 SECONDS + melee_cooldown_time = 0 SECONDS + +/datum/action/cooldown/worm_contract/Activate(atom/target) + SEND_SIGNAL(owner, COMSIG_MOB_CHAIN_CONTRACT) + StartCooldown() diff --git a/code/datums/components/rot.dm b/code/datums/components/rot.dm index 11c4f2cb617..64ddde2b2db 100644 --- a/code/datums/components/rot.dm +++ b/code/datums/components/rot.dm @@ -114,7 +114,7 @@ /datum/component/rot/proc/rot_react_touch(datum/source, mob/living/react_to) SIGNAL_HANDLER - rot_react(source, react_to, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + rot_react(source, react_to, pick(GLOB.arm_zones)) /// Triggered when something enters the component's parent. /datum/component/rot/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) diff --git a/code/datums/elements/amputating_limbs.dm b/code/datums/elements/amputating_limbs.dm index c2fe7c454a9..f7e3d08fcc4 100644 --- a/code/datums/elements/amputating_limbs.dm +++ b/code/datums/elements/amputating_limbs.dm @@ -6,6 +6,10 @@ var/surgery_time /// What is the means by which we describe the act of amputation? var/surgery_verb + /// How awake must our target be? + var/minimum_stat + /// How likely are we to perform this action? + var/snip_chance /// The types of limb we can remove var/list/target_zones @@ -13,6 +17,8 @@ datum/target, surgery_time = 5 SECONDS, surgery_verb = "prying", + minimum_stat = SOFT_CRIT, + snip_chance = 100, list/target_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG), ) . = ..() @@ -23,6 +29,8 @@ src.surgery_time = surgery_time src.surgery_verb = surgery_verb + src.minimum_stat = minimum_stat + src.snip_chance = snip_chance src.target_zones = target_zones RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(try_amputate)) @@ -33,11 +41,11 @@ /// Called when you click on literally anything with your hands, see if it is an injured carbon and then try to cut it up /datum/element/amputating_limbs/proc/try_amputate(mob/living/surgeon, atom/victim) SIGNAL_HANDLER - if (!iscarbon(victim) || HAS_TRAIT(victim, TRAIT_NODISMEMBER)) + if (!iscarbon(victim) || HAS_TRAIT(victim, TRAIT_NODISMEMBER) || !prob(snip_chance)) return var/mob/living/carbon/limbed_victim = victim - if (limbed_victim.stat == CONSCIOUS) + if (limbed_victim.stat < minimum_stat) return if (DOING_INTERACTION_WITH_TARGET(surgeon, victim)) @@ -61,6 +69,6 @@ /// Chop one off /datum/element/amputating_limbs/proc/amputate(mob/living/surgeon, mob/living/carbon/victim, obj/item/bodypart/to_remove) surgeon.visible_message(span_warning("[surgeon] begins [surgery_verb] [to_remove] off of [victim]!")) - if (!do_after(surgeon, delay = surgery_time, target = victim)) + if (surgery_time > 0 && !do_after(surgeon, delay = surgery_time, target = victim)) return to_remove.dismember() diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm index 89b7ba5762b..fb543aa009e 100644 --- a/code/datums/wounds/_wounds.dm +++ b/code/datums/wounds/_wounds.dm @@ -435,7 +435,7 @@ else limp_slowdown = initial(limp_slowdown) limp_chance = initial(limp_chance) - else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + else if(limb.body_zone in GLOB.arm_zones) if(limb.current_gauze?.splint_factor) set_interaction_efficiency_penalty(1 + ((get_effective_actionspeed_modifier()) * limb.current_gauze.splint_factor)) else diff --git a/code/game/objects/items/devices/polycircuit.dm b/code/game/objects/items/devices/polycircuit.dm index 9dbdbff993d..5b7fd42d6f6 100644 --- a/code/game/objects/items/devices/polycircuit.dm +++ b/code/game/objects/items/devices/polycircuit.dm @@ -51,7 +51,7 @@ else to_chat(user, span_notice("You navigate the sharp edges of circuitry and remove a single board from [src]")) else - H.apply_damage(15, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + H.apply_damage(15, BRUTE, pick(GLOB.arm_zones)) to_chat(user, span_warning("You give yourself a wicked cut on [src]'s many sharp corners and edges!")) /obj/item/stack/circuit_stack/full diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 7f135cdbefb..cb78f59f89a 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -58,6 +58,9 @@ GLOBAL_PROTECT(admin_verbs_admin) /client/proc/admin_enable_shuttle, /*undoes the above*/ /client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/ /client/proc/admin_hostile_environment, /*Allows admins to prevent the emergency shuttle from leaving, also lets admins clear hostile environments if theres one stuck*/ + /client/proc/centcom_podlauncher,/*Open a window to launch a Supplypod and configure it or it's contents*/ + /client/proc/check_ai_laws, /*shows AI and borg laws*/ + /client/proc/check_antagonists, /*shows all antags*/ /client/proc/cmd_admin_check_contents, /*displays the contents of an instance*/ /client/proc/cmd_admin_check_player_exp, /* shows players by playtime */ /client/proc/cmd_admin_create_centcom_report, @@ -68,9 +71,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /client/proc/cmd_admin_subtle_message, /*send a message to somebody as a 'voice in their head'*/ /client/proc/cmd_admin_world_narrate, /*sends text to all players with no padding*/ /client/proc/cmd_change_command_name, - /client/proc/centcom_podlauncher,/*Open a window to launch a Supplypod and configure it or it's contents*/ - /client/proc/check_ai_laws, /*shows AI and borg laws*/ - /client/proc/check_antagonists, /*shows all antags*/ + /client/proc/create_mob_worm, /client/proc/fax_panel, /*send a paper to fax*/ /client/proc/force_load_lazy_template, /client/proc/game_panel, /*game panel, allows to change game-mode etc*/ @@ -1174,3 +1175,48 @@ GLOBAL_PROTECT(admin_verbs_poll) holder.library_manager = new() holder.library_manager.ui_interact(usr) SSblackbox.record_feedback("tally", "admin_verb", 1, "Library Management") // If you are copy-pasting this, ensure the 4th parameter is unique to the new proc! + +/client/proc/create_mob_worm() + set category = "Admin.Fun" + set name = "Create Mob Worm" + set desc = "Attached a linked list of mobs to a marked mob" + if (!check_rights(R_FUN)) + return + if(isnull(holder)) + return + if(!isliving(holder.marked_datum)) + to_chat(usr, span_warning("Error: Please mark a mob to attach mobs to.")) + return + var/mob/living/head = holder.marked_datum + + var/attempted_target_path = tgui_input_text( + usr, + "Enter typepath of a mob you'd like to make your chain from.", + "Typepath", + "[/mob/living/basic/pet/dog/corgi/ian]", + ) + + if (isnull(attempted_target_path)) + return //The user pressed "Cancel" + + var/desired_mob = text2path(attempted_target_path) + if(!ispath(desired_mob)) + var/static/list/mob_paths = make_types_fancy(subtypesof(/mob/living)) + desired_mob = pick_closest_path(attempted_target_path, mob_paths) + if(isnull(desired_mob) || !ispath(desired_mob) || QDELETED(head)) + return //The user pressed "Cancel" + + var/amount = tgui_input_number(usr, "How long should our tail be?", "Worm Configurator", default = 3, min_value = 1) + if (isnull(amount) || amount < 1 || QDELETED(head)) + return + head.AddComponent(/datum/component/mob_chain) + var/mob/living/previous = head + for (var/i in 1 to amount) + var/mob/living/segment = new desired_mob(head.drop_location()) + if (QDELETED(segment)) // ffs mobs which replace themselves with other mobs + i-- + continue + ADD_TRAIT(segment, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) + QDEL_NULL(segment.ai_controller) + segment.AddComponent(/datum/component/mob_chain, front = previous) + previous = segment diff --git a/code/modules/antagonists/cult/cult_bastard_sword.dm b/code/modules/antagonists/cult/cult_bastard_sword.dm index a30ffb1f5ee..784eaedf636 100644 --- a/code/modules/antagonists/cult/cult_bastard_sword.dm +++ b/code/modules/antagonists/cult/cult_bastard_sword.dm @@ -80,7 +80,7 @@ to_chat(user, span_cultlarge("\"You cling to the Forgotten Gods, as if you're more than their pawn.\"")) to_chat(user, span_userdanger("A horrible force yanks at your arm!")) user.emote("scream") - user.apply_damage(30, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + user.apply_damage(30, BRUTE, pick(GLOB.arm_zones)) user.dropItemToGround(src, TRUE) user.Paralyze(50) return diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index c3175d0ed4a..e5adcf0cfc5 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -100,7 +100,7 @@ Striking a noncultist, however, will tear their flesh."} span_cultlarge("\"You shouldn't play with sharp things. You'll poke someone's eye out.\"")) if(ishuman(user)) var/mob/living/carbon/human/miscreant = user - miscreant.apply_damage(rand(force/2, force), BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + miscreant.apply_damage(rand(force/2, force), BRUTE, pick(GLOB.arm_zones)) else user.adjustBruteLoss(rand(force/2,force)) return diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 062412f70e9..de8a26187ae 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -697,7 +697,7 @@ structure_check() searches for nearby cultist structures required for the invoca barrier.Toggle() if(iscarbon(user)) var/mob/living/carbon/C = user - C.apply_damage(2, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + C.apply_damage(2, BRUTE, pick(GLOB.arm_zones)) //Rite of Joined Souls: Summons a single cultist. /obj/effect/rune/summon diff --git a/code/modules/antagonists/heretic/magic/flesh_ascension.dm b/code/modules/antagonists/heretic/magic/flesh_ascension.dm index cb9ab63e031..d086c1127fc 100644 --- a/code/modules/antagonists/heretic/magic/flesh_ascension.dm +++ b/code/modules/antagonists/heretic/magic/flesh_ascension.dm @@ -13,7 +13,7 @@ invocation_type = INVOCATION_SHOUT spell_requirements = NONE - possible_shapes = list(/mob/living/simple_animal/hostile/heretic_summon/armsy/prime) + possible_shapes = list(/mob/living/basic/heretic_summon/armsy) /// The length of our new wormy when we shed. var/segment_length = 10 @@ -35,32 +35,11 @@ return ..() -/datum/action/cooldown/spell/shapeshift/shed_human_form/do_unshapeshift(mob/living/simple_animal/hostile/heretic_summon/armsy/caster) +/datum/action/cooldown/spell/shapeshift/shed_human_form/do_unshapeshift(mob/living/basic/heretic_summon/armsy/caster) if(istype(caster)) - segment_length = caster.get_length() + segment_length = caster.get_length() - 1 // Don't count the head return ..() /datum/action/cooldown/spell/shapeshift/shed_human_form/create_shapeshift_mob(atom/loc) return new shapeshift_type(loc, TRUE, segment_length) - -/datum/action/cooldown/spell/worm_contract - name = "Force Contract" - desc = "Forces your body to contract onto a single tile." - background_icon_state = "bg_heretic" - overlay_icon_state = "bg_heretic_border" - button_icon = 'icons/mob/actions/actions_ecult.dmi' - button_icon_state = "worm_contract" - - school = SCHOOL_FORBIDDEN - cooldown_time = 30 SECONDS - - invocation_type = INVOCATION_NONE - spell_requirements = NONE - -/datum/action/cooldown/spell/worm_contract/is_valid_target(atom/cast_on) - return istype(cast_on, /mob/living/simple_animal/hostile/heretic_summon/armsy) - -/datum/action/cooldown/spell/worm_contract/cast(mob/living/simple_animal/hostile/heretic_summon/armsy/cast_on) - . = ..() - cast_on.contract_next_chain_into_single_tile() diff --git a/code/modules/antagonists/heretic/structures/knock_final.dm b/code/modules/antagonists/heretic/structures/knock_final.dm index c8a2058eb9f..28a8f90b9f3 100644 --- a/code/modules/antagonists/heretic/structures/knock_final.dm +++ b/code/modules/antagonists/heretic/structures/knock_final.dm @@ -20,9 +20,8 @@ var/static/list/monster_types /// A static list of heretic summons which we should not create var/static/list/monster_types_blacklist = list( + /mob/living/basic/heretic_summon/armsy, /mob/living/basic/heretic_summon/star_gazer, - /mob/living/simple_animal/hostile/heretic_summon/armsy, - /mob/living/simple_animal/hostile/heretic_summon/armsy/prime, ) /obj/structure/knock_tear/Initialize(mapload, datum/mind/ascendant_mind) diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index aa88961e3c4..118c28c0e51 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -208,7 +208,7 @@ to_chat(our_guy, span_userdanger("You stamp on [user]'s hand! What the- [user.p_they()] [user.p_were()] [tied ? "knotting" : "untying"] your shoelaces!")) user.emote("scream") if(istype(L)) - var/obj/item/bodypart/ouchie = L.get_bodypart(pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + var/obj/item/bodypart/ouchie = L.get_bodypart(pick(GLOB.arm_zones)) if(ouchie) ouchie.receive_damage(brute = 10) L.adjustStaminaLoss(40) diff --git a/code/modules/mob/living/basic/health_adjustment.dm b/code/modules/mob/living/basic/health_adjustment.dm index e453bf7306e..4ae129d9d66 100644 --- a/code/modules/mob/living/basic/health_adjustment.dm +++ b/code/modules/mob/living/basic/health_adjustment.dm @@ -1,5 +1,5 @@ /** - * Adjusts the health of a simple mob by a set amount and wakes AI if its idle to react + * Adjusts the health of a simple mob by a set amount * * Arguments: * * amount The amount that will be used to adjust the mob's health @@ -9,44 +9,65 @@ */ /mob/living/basic/proc/adjust_health(amount, updating_health = TRUE, forced = FALSE) . = FALSE - if(forced || !(status_flags & GODMODE)) - . = bruteloss // bruteloss value before applying damage - bruteloss = round(clamp(bruteloss + amount, 0, maxHealth * 2), DAMAGE_PRECISION) - if(updating_health) - updatehealth() - . -= bruteloss + if(!forced && (status_flags & GODMODE)) + return 0 + . = bruteloss // bruteloss value before applying damage + bruteloss = round(clamp(bruteloss + amount, 0, maxHealth * 2), DAMAGE_PRECISION) + if(updating_health) + updatehealth() + return . - bruteloss /mob/living/basic/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(!forced && (status_flags & GODMODE)) + return 0 + if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BRUTE]) . = adjust_health(amount * damage_coeff[BRUTE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(!forced && (status_flags & GODMODE)) + return 0 + if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BURN]) . = adjust_health(amount * damage_coeff[BURN] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype, required_respiration_type) + if(!forced && (status_flags & GODMODE)) + return 0 + if(on_damage_adjustment(OXY, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[OXY]) . = adjust_health(amount * damage_coeff[OXY] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) + if(!forced && (status_flags & GODMODE)) + return 0 + if(on_damage_adjustment(TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[TOX]) . = adjust_health(amount * damage_coeff[TOX] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) + if(on_damage_adjustment(CLONE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[CLONE]) . = adjust_health(amount * damage_coeff[CLONE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/basic/adjustStaminaLoss(amount, updating_stamina = TRUE, forced = FALSE, required_biotype) + if(on_damage_adjustment(STAMINA, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 . = staminaloss if(forced) staminaloss = max(0, min(BASIC_MOB_MAX_STAMINALOSS, staminaloss + amount)) diff --git a/code/modules/mob/living/basic/heretic/flesh_worm.dm b/code/modules/mob/living/basic/heretic/flesh_worm.dm new file mode 100644 index 00000000000..05ef707faf4 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/flesh_worm.dm @@ -0,0 +1,137 @@ +/// Armsy starts to look a bit funky if he's shorter than this +#define MINIMUM_ARMSY_LENGTH 2 + +// What if we took a linked list... But made it a mob? +/// The "Terror of the Night" / Armsy, a large worm made of multiple bodyparts that occupies multiple tiles +/mob/living/basic/heretic_summon/armsy + name = "Lord of the Night" + real_name = "Master of Decay" + desc = "An abomination made from dozens and dozens of severed and malformed limbs grasping onto each other." + icon_state = "armsy_start" + icon_living = "armsy_start" + base_icon_state = "armsy" + maxHealth = 400 + health = 400 + melee_damage_lower = 30 + melee_damage_upper = 50 + obj_damage = 200 + move_force = MOVE_FORCE_OVERPOWERING + move_resist = MOVE_FORCE_OVERPOWERING + pull_force = MOVE_FORCE_OVERPOWERING + mob_size = MOB_SIZE_HUGE + sentience_type = SENTIENCE_BOSS + mob_biotypes = MOB_ORGANIC|MOB_SPECIAL + ///Previous segment in the chain, we hold onto this purely to keep track of how long we currently are and to attach new growth to the back + var/mob/living/basic/heretic_summon/armsy/back + ///How many arms do we have to eat to expand? + var/stacks_to_grow = 5 + ///Currently eaten arms + var/current_stacks = 0 +/* + * Arguments + * * spawn_bodyparts - whether we spawn additional armsy bodies until we reach length. + * * worm_length - the length of the worm we're creating. Below 2 doesn't work very well. + */ +/mob/living/basic/heretic_summon/armsy/Initialize(mapload, spawn_bodyparts = TRUE, worm_length = 6) + . = ..() + AddElement(/datum/element/wall_smasher, ENVIRONMENT_SMASH_RWALLS) + AddElement(\ + /datum/element/amputating_limbs,\ + surgery_time = 0 SECONDS,\ + surgery_verb = "tearing",\ + minimum_stat = CONSCIOUS,\ + snip_chance = 10,\ + target_zones = GLOB.arm_zones,\ + ) + AddComponent(\ + /datum/component/blood_walk, \ + blood_type = /obj/effect/decal/cleanable/blood/tracks, \ + target_dir_change = TRUE,\ + ) + + if(spawn_bodyparts) + build_tail(worm_length) + +// We are a vessel of otherworldly destruction, we bring our gravity with us +/mob/living/basic/heretic_summon/armsy/has_gravity(turf/gravity_turf) + return TRUE + +/mob/living/basic/heretic_summon/armsy/can_be_pulled() + return FALSE // The component does this but not on the head. We don't want the head to be pulled either. + +/mob/living/basic/heretic_summon/armsy/proc/build_tail(worm_length) + worm_length = max(worm_length, MINIMUM_ARMSY_LENGTH) + // Sets the hp of the head to be exactly the (length * hp), so the head is de facto the hardest to destroy. + maxHealth = worm_length * maxHealth + health = maxHealth + + AddComponent(/datum/component/mob_chain, vary_icon_state = TRUE) // We're the front + + var/mob/living/basic/heretic_summon/armsy/prev = src + for(var/i in 1 to worm_length) + prev = new_segment(behind = prev) + update_appearance(UPDATE_ICON_STATE) + +/// Grows a new segment behind the passed mob +/mob/living/basic/heretic_summon/armsy/proc/new_segment(mob/living/basic/heretic_summon/armsy/behind) + var/mob/living/segment = new type(drop_location(), FALSE) + ADD_TRAIT(segment, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) + segment.AddComponent(/datum/component/mob_chain, front = behind, vary_icon_state = TRUE) + behind.register_behind(segment) + return segment + +/// Record that we got another guy on our ass +/mob/living/basic/heretic_summon/armsy/proc/register_behind(mob/living/tail) + if(!isnull(back)) // Shouldn't happen but just in case + UnregisterSignal(back, COMSIG_QDELETING) + back = tail + update_appearance(UPDATE_ICON_STATE) + if(!isnull(back)) + RegisterSignal(back, COMSIG_QDELETING, PROC_REF(tail_deleted)) + +/// When our tail is gone stop holding a reference to it +/mob/living/basic/heretic_summon/armsy/proc/tail_deleted() + SIGNAL_HANDLER + register_behind(null) + +/mob/living/basic/heretic_summon/armsy/melee_attack(atom/target, list/modifiers, ignore_cooldown) + if(!istype(target, /obj/item/bodypart/arm)) + return ..() + visible_message(span_warning("[src] devours [target]!")) + playsound(src, 'sound/magic/demon_consume.ogg', 50, TRUE) + qdel(target) + on_arm_eaten() + +/* + * Handle healing our chain. + * Eating arms off the ground heals us, and if we eat enough arms while above a certain health threshold we get longer! + */ +/mob/living/basic/heretic_summon/armsy/proc/on_arm_eaten() + if(!isnull(back)) + back.on_arm_eaten() + return + + adjustBruteLoss(-maxHealth * 0.5, FALSE) + adjustFireLoss(-maxHealth * 0.5, FALSE) + + if(health < maxHealth * 0.8) + return + + current_stacks++ + if(current_stacks < stacks_to_grow) + return + + visible_message(span_boldwarning("[src] flexes and expands!")) + current_stacks = 0 + new_segment(behind = src) + +/* + * Recursively get the length of our chain. + */ +/mob/living/basic/heretic_summon/armsy/proc/get_length() + . = 1 + if(isnull(back)) + return + . += back.get_length() + +#undef MINIMUM_ARMSY_LENGTH diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index 18cd7321936..d47ae15b975 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -28,8 +28,6 @@ ai_controller = /datum/ai_controller/basic_controller/lobstrosity /// Charging ability var/datum/action/cooldown/mob_cooldown/charge/basic_charge/lobster/charge - /// Limbs we will cut off an unconscious man - var/static/list/target_limbs = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) /// Things we will eat if we see them (arms, chiefly) var/static/list/target_foods = list(/obj/item/bodypart/arm) @@ -42,7 +40,7 @@ AddElement(\ /datum/element/amputating_limbs,\ surgery_verb = "snipping",\ - target_zones = target_limbs,\ + target_zones = GLOB.arm_zones,\ ) charge = new(src) charge.Grant(src) diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm index 963f064286b..d297296a4ec 100644 --- a/code/modules/mob/living/carbon/damage_procs.dm +++ b/code/modules/mob/living/carbon/damage_procs.dm @@ -57,7 +57,9 @@ /mob/living/carbon/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) if(!forced && (status_flags & GODMODE)) - return FALSE + return 0 + if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(amount > 0) . = take_overall_damage(brute = amount, updating_health = updating_health, forced = forced, required_bodytype = required_bodytype) else @@ -74,7 +76,9 @@ /mob/living/carbon/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) if(!forced && (status_flags & GODMODE)) - return FALSE + return 0 + if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(amount > 0) . = take_overall_damage(burn = amount, updating_health = updating_health, forced = forced, required_bodytype = required_bodytype) else @@ -91,9 +95,11 @@ /mob/living/carbon/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) if(!forced && (status_flags & GODMODE)) - return FALSE + return 0 if(!forced && !(mob_biotypes & required_biotype)) - return FALSE + return 0 + if(on_damage_adjustment(TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(!forced && HAS_TRAIT(src, TRAIT_TOXINLOVER)) //damage becomes healing and healing becomes damage amount = -amount if(HAS_TRAIT(src, TRAIT_TOXIMMUNE)) //Prevents toxin damage, but not healing diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 9daab06a5a6..82425b27a5b 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -159,13 +159,18 @@ return TRUE +/// Should be called by any adjustXLoss proc to send signalling information, returns a bit flag which may indicate that we don't want to make any adjustment +/mob/living/proc/on_damage_adjustment(damage_type, amount, forced) + return SEND_SIGNAL(src, COMSIG_LIVING_ADJUST_DAMAGE, damage_type, amount, forced) /mob/living/proc/getBruteLoss() return bruteloss /mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) if(!forced && (status_flags & GODMODE)) - return FALSE + return 0 + if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 . = bruteloss bruteloss = clamp((bruteloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) . -= bruteloss @@ -193,15 +198,17 @@ /mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL, required_respiration_type = ALL) if(!forced) if(status_flags & GODMODE) - return FALSE + return 0 var/obj/item/organ/internal/lungs/affected_lungs = get_organ_slot(ORGAN_SLOT_LUNGS) if(isnull(affected_lungs)) if(!(mob_respiration_type & required_respiration_type)) // if the mob has no lungs, use mob_respiration_type - return FALSE + return 0 else if(!(affected_lungs.respiration_type & required_respiration_type)) // otherwise use the lungs' respiration_type - return FALSE + return 0 + if(on_damage_adjustment(OXY, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 . = oxyloss oxyloss = clamp((oxyloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) . -= oxyloss @@ -236,6 +243,8 @@ /mob/living/proc/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) if(!forced && (status_flags & GODMODE)) return FALSE + if(on_damage_adjustment(TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(!forced && !(mob_biotypes & required_biotype)) return FALSE . = toxloss @@ -264,23 +273,25 @@ /mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) if(!forced && (status_flags & GODMODE)) - return FALSE + return 0 + if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 . = fireloss fireloss = clamp((fireloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) . -= fireloss - if(!.) // no change, no need to update - return FALSE + if(. == 0) // no change, no need to update + return if(updating_health) updatehealth() /mob/living/proc/setFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) if(!forced && (status_flags & GODMODE)) - return FALSE + return 0 . = fireloss fireloss = amount . -= fireloss - if(!.) // no change, no need to update - return FALSE + if(. == 0) // no change, no need to update + return 0 if(updating_health) updatehealth() @@ -288,15 +299,15 @@ return cloneloss /mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) - if(!forced && ( (status_flags & GODMODE) || HAS_TRAIT(src, TRAIT_NOCLONELOSS)) ) - return FALSE - if(!forced && !(mob_biotypes & required_biotype)) - return FALSE + if(!forced && (!(mob_biotypes & required_biotype) || status_flags & GODMODE || HAS_TRAIT(src, TRAIT_NOCLONELOSS))) + return 0 + if(on_damage_adjustment(CLONE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 . = cloneloss cloneloss = clamp((cloneloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) . -= cloneloss - if(!.) // no change, no need to update - return FALSE + if(. == 0) // no change, no need to update + return 0 if(updating_health) updatehealth() @@ -326,15 +337,15 @@ return staminaloss /mob/living/proc/adjustStaminaLoss(amount, updating_stamina = TRUE, forced = FALSE, required_biotype = ALL) - if(!forced && (status_flags & GODMODE)) - return FALSE - if(!forced && !(mob_biotypes & required_biotype)) - return FALSE + if(!forced && ((status_flags & GODMODE) || required_biotype && !(mob_biotypes & required_biotype))) + return 0 + if(on_damage_adjustment(STAMINA, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 . = staminaloss staminaloss = clamp((staminaloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, max_stamina) . -= staminaloss - if(!.) // no change, no need to update - return FALSE + if(. == 0) // no change, no need to update + return 0 if(updating_stamina) updatehealth() diff --git a/code/modules/mob/living/simple_animal/damage_procs.dm b/code/modules/mob/living/simple_animal/damage_procs.dm index 1f7638325ce..0c06049288d 100644 --- a/code/modules/mob/living/simple_animal/damage_procs.dm +++ b/code/modules/mob/living/simple_animal/damage_procs.dm @@ -19,36 +19,48 @@ toggle_ai(AI_ON) /mob/living/simple_animal/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BRUTE]) . = adjustHealth(amount * damage_coeff[BRUTE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[BURN]) . = adjustHealth(amount * damage_coeff[BURN] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype, required_respiration_type) + if(on_damage_adjustment(OXY, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[OXY]) . = adjustHealth(amount * damage_coeff[OXY] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) + if(on_damage_adjustment(TOX, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[TOX]) . = adjustHealth(amount * damage_coeff[TOX] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype) + if(on_damage_adjustment(CLONE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) . = adjustHealth(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) else if(damage_coeff[CLONE]) . = adjustHealth(amount * damage_coeff[CLONE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) /mob/living/simple_animal/adjustStaminaLoss(amount, updating_stamina = TRUE, forced = FALSE, required_biotype) + if(on_damage_adjustment(STAMINA, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) staminaloss = max(0, min(max_staminaloss, staminaloss + amount)) else diff --git a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm index 741e698a568..090709d0527 100644 --- a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm +++ b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm @@ -43,232 +43,6 @@ var/datum/action/cooldown/spell/new_spell = new spell(src) new_spell.Grant(src) -// What if we took a linked list... But made it a mob? -/// The "Terror of the Night" / Armsy, a large worm made of multiple bodyparts that occupies multiple tiles -/mob/living/simple_animal/hostile/heretic_summon/armsy - name = "Terror of the night" - real_name = "Armsy" - desc = "An abomination made from dozens and dozens of severed and malformed limbs piled onto each other." - icon_state = "armsy_start" - icon_living = "armsy_start" - maxHealth = 200 - health = 200 - melee_damage_lower = 10 - melee_damage_upper = 15 - move_force = MOVE_FORCE_OVERPOWERING - move_resist = MOVE_FORCE_OVERPOWERING - pull_force = MOVE_FORCE_OVERPOWERING - movement_type = GROUND - mob_size = MOB_SIZE_HUGE - sentience_type = SENTIENCE_BOSS - environment_smash = ENVIRONMENT_SMASH_RWALLS - mob_biotypes = MOB_ORGANIC|MOB_SPECIAL - obj_damage = 200 - ranged_cooldown_time = 5 - ranged = TRUE - rapid = 1 - actions_to_add = list(/datum/action/cooldown/spell/worm_contract) - ///Previous segment in the chain - var/mob/living/simple_animal/hostile/heretic_summon/armsy/back - ///Next segment in the chain - var/mob/living/simple_animal/hostile/heretic_summon/armsy/front - ///Your old location - var/oldloc - ///Allow / disallow pulling - var/allow_pulling = FALSE - ///How many arms do we have to eat to expand? - var/stacks_to_grow = 5 - ///Currently eaten arms - var/current_stacks = 0 - ///Does this follow other pieces? - var/follow = TRUE - -/* - * Arguments - * * spawn_bodyparts - whether we spawn additional armsy bodies until we reach length. - * * worm_length - the length of the worm we're creating. Below 3 doesn't work very well. - */ -/mob/living/simple_animal/hostile/heretic_summon/armsy/Initialize(mapload, spawn_bodyparts = TRUE, worm_length = 6) - . = ..() - if(worm_length < 3) - stack_trace("[type] created with invalid len ([worm_length]). Reverting to 3.") - worm_length = 3 //code breaks below 3, let's just not allow it. - - oldloc = loc - RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(update_chain_links)) - if(!spawn_bodyparts) - return - - AddComponent(/datum/component/blood_walk, \ - blood_type = /obj/effect/decal/cleanable/blood/tracks, \ - target_dir_change = TRUE) - - allow_pulling = TRUE - // Sets the hp of the head to be exactly the (length * hp), so the head is de facto the hardest to destroy. - maxHealth = worm_length * maxHealth - health = maxHealth - - // The previous link in the chain - var/mob/living/simple_animal/hostile/heretic_summon/armsy/prev = src - // The current link in the chain - var/mob/living/simple_animal/hostile/heretic_summon/armsy/current - - for(var/i in 1 to worm_length) - current = new type(drop_location(), FALSE) - ADD_TRAIT(current, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) - current.icon_state = "armsy_mid" - current.icon_living = "armsy_mid" - current.AIStatus = AI_OFF - current.front = prev - prev.back = current - prev = current - - prev.icon_state = "armsy_end" - prev.icon_living = "armsy_end" - -/mob/living/simple_animal/hostile/heretic_summon/armsy/adjustBruteLoss(amount, updating_health, forced, required_bodytype) - if(back) - return back.adjustBruteLoss(amount, updating_health, forced) - - return ..() - -/mob/living/simple_animal/hostile/heretic_summon/armsy/adjustFireLoss(amount, updating_health, forced, required_bodytype) - if(back) - return back.adjustFireLoss(amount, updating_health, forced) - - return ..() - -// We are literally a vessel of otherworldly destruction, we bring our own gravity unto this plane -/mob/living/simple_animal/hostile/heretic_summon/armsy/has_gravity(turf/gravity_turf) - return TRUE - -/mob/living/simple_animal/hostile/heretic_summon/armsy/can_be_pulled() - return FALSE - -/// Updates every body in the chain to force move onto a single tile. -/mob/living/simple_animal/hostile/heretic_summon/armsy/proc/contract_next_chain_into_single_tile() - if(!back) - return - - back.forceMove(loc) - back.contract_next_chain_into_single_tile() - -/* - * Recursively get the length of our chain. - */ -/mob/living/simple_animal/hostile/heretic_summon/armsy/proc/get_length() - . = 1 - if(back) - . += back.get_length() - -/// Updates the next mob in the chain to move to our last location. Fixes the chain if somehow broken. -/mob/living/simple_animal/hostile/heretic_summon/armsy/proc/update_chain_links() - SIGNAL_HANDLER - - if(!follow) - return - - if(back && back.loc != oldloc) - back.Move(oldloc) - - // self fixing properties if somehow broken - if(front && loc != front.oldloc) - forceMove(front.oldloc) - - oldloc = loc - -/mob/living/simple_animal/hostile/heretic_summon/armsy/Destroy() - if(front) - front.icon_state = "armsy_end" - front.icon_living = "armsy_end" - front.back = null - front = null - if(back) - QDEL_NULL(back) // chain destruction baby - return ..() - -/* - * Handle healing our chain. - * - * Eating arms off the ground heals us, - * and if we eat enough arms while above - * a certain health threshold, we even gain back parts! - */ -/mob/living/simple_animal/hostile/heretic_summon/armsy/proc/heal() - if(back) - back.heal() - return - - adjustBruteLoss(-maxHealth * 0.5, FALSE) - adjustFireLoss(-maxHealth * 0.5, FALSE) - - if(health < maxHealth * 0.8) - return - - current_stacks++ - if(current_stacks < stacks_to_grow) - return - - var/mob/living/simple_animal/hostile/heretic_summon/armsy/prev = new type(drop_location(), FALSE) - icon_state = "armsy_mid" - icon_living = "armsy_mid" - back = prev - prev.icon_state = "armsy_end" - prev.icon_living = "armsy_end" - prev.front = src - prev.AIStatus = AI_OFF - current_stacks = 0 - -/mob/living/simple_animal/hostile/heretic_summon/armsy/Shoot(atom/targeted_atom) - GiveTarget(targeted_atom) - AttackingTarget() - -/mob/living/simple_animal/hostile/heretic_summon/armsy/AttackingTarget() - if(istype(target, /obj/item/bodypart/arm)) - playsound(src, 'sound/magic/demon_consume.ogg', 50, TRUE) - qdel(target) - heal() - return - if(target == back || target == front) - return - if(back) - back.GiveTarget(target) - back.AttackingTarget() - if(!Adjacent(target)) - return - do_attack_animation(target) - - if(iscarbon(target)) - var/mob/living/carbon/carbon_target = target - if(HAS_TRAIT(carbon_target, TRAIT_NODISMEMBER)) - return ..() - - var/list/parts_to_remove = list() - for(var/obj/item/bodypart/bodypart in carbon_target.bodyparts) - if(bodypart.body_part != HEAD && bodypart.body_part != CHEST && bodypart.body_part != LEG_LEFT && bodypart.body_part != LEG_RIGHT) - if(!(bodypart.bodypart_flags & BODYPART_UNREMOVABLE)) - parts_to_remove += bodypart - - if(parts_to_remove.len && prob(10)) - var/obj/item/bodypart/lost_arm = pick(parts_to_remove) - lost_arm.dismember() - - return ..() - -/mob/living/simple_animal/hostile/heretic_summon/armsy/prime - name = "Lord of the Night" - real_name = "Master of Decay" - maxHealth = 400 - health = 400 - melee_damage_lower = 30 - melee_damage_upper = 50 - -/mob/living/simple_animal/hostile/heretic_summon/armsy/prime/Initialize(mapload, spawn_bodyparts = TRUE, worm_length = 9) - . = ..() - var/matrix/matrix_transformation = matrix() - matrix_transformation.Scale(1.4, 1.4) - transform = matrix_transformation - /mob/living/simple_animal/hostile/heretic_summon/rust_spirit name = "Rust Walker" real_name = "Rusty" diff --git a/code/modules/pai/defense.dm b/code/modules/pai/defense.dm index 3a888fa9c83..b5eb177fddc 100644 --- a/code/modules/pai/defense.dm +++ b/code/modules/pai/defense.dm @@ -65,7 +65,7 @@ /mob/living/silicon/pai/ignite_mob(silent) return FALSE -/mob/living/silicon/pai/proc/take_holo_damage(amount) +/mob/living/silicon/pai/proc/take_holo_damage(type, amount) holochassis_health = clamp((holochassis_health - amount), -50, HOLOCHASSIS_MAX_HEALTH) if(holochassis_health < 0) fold_in(force = TRUE) @@ -74,12 +74,18 @@ return amount /mob/living/silicon/pai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(on_damage_adjustment(BRUTE, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 return take_holo_damage(amount) /mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(on_damage_adjustment(BURN, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 return take_holo_damage(amount) /mob/living/silicon/pai/adjustStaminaLoss(amount, updating_stamina, forced = FALSE, required_biotype) + if(on_damage_adjustment(STAMINA, amount, forced) & COMPONENT_IGNORE_CHANGE) + return 0 if(forced) take_holo_damage(amount) else diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 61a23a81a41..56e1493fc74 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -460,10 +460,14 @@ var/hit_percent = (100-blocked)/100 if((!brute && !burn) || hit_percent <= 0) return FALSE - if(!forced && owner && (owner.status_flags & GODMODE)) - return FALSE //godmode - if(!forced && required_bodytype && !(bodytype & required_bodytype)) - return FALSE + if (!forced) + if(!isnull(owner)) + if (owner.status_flags & GODMODE) + return FALSE + if (SEND_SIGNAL(owner, COMSIG_CARBON_LIMB_DAMAGED, src, brute, burn) & COMPONENT_PREVENT_LIMB_DAMAGE) + return FALSE + if(required_bodytype && !(bodytype & required_bodytype)) + return FALSE var/dmg_multi = CONFIG_GET(number/damage_multiplier) * hit_percent brute = round(max(brute * dmg_multi * brute_modifier, 0), DAMAGE_PRECISION) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index e05c2516045..98b5e7f1b3c 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -175,6 +175,7 @@ #include "metabolizing.dm" #include "mindbound_actions.dm" #include "missing_icons.dm" +#include "mob_chains.dm" #include "mob_damage.dm" #include "mob_faction.dm" #include "mob_spawn.dm" diff --git a/code/modules/unit_tests/mob_chains.dm b/code/modules/unit_tests/mob_chains.dm new file mode 100644 index 00000000000..2562019958e --- /dev/null +++ b/code/modules/unit_tests/mob_chains.dm @@ -0,0 +1,31 @@ +/// Checks if mobs who are linked together with the mob chain component react as expected +/datum/unit_test/mob_chains + +/datum/unit_test/mob_chains/Run() + var/mob/living/centipede_head = allocate(/mob/living/basic/pet/dog) + var/list/segments = list(centipede_head) + centipede_head.AddComponent(/datum/component/mob_chain) + var/mob/living/centipede_tail = centipede_head + for (var/i in 1 to 2) + var/mob/living/new_segment = allocate(/mob/living/basic/pet/dog) + new_segment.AddComponent(/datum/component/mob_chain, front = centipede_tail) + segments += new_segment + centipede_tail = new_segment + + var/test_damage = 15 + centipede_head.apply_damage(test_damage, BRUTE) + TEST_ASSERT_EQUAL(centipede_head.bruteloss, 0, "Centipede head took damage which should have been passed to its tail.") + TEST_ASSERT_EQUAL(centipede_tail.bruteloss, test_damage, "Centipede tail did not take damage which should have originated from its head.") + + var/expected_damage = 5 + for (var/mob/living/segment as anything in segments) + segment.combat_mode = TRUE + segment.melee_damage_lower = expected_damage + segment.melee_damage_upper = expected_damage + + var/mob/living/victim = allocate(/mob/living/basic/pet/dog) + centipede_head.ClickOn(victim) + TEST_ASSERT_EQUAL(victim.bruteloss, expected_damage * 3, "Centipede failed to do damage with all of its segments.") + + centipede_head.death() + TEST_ASSERT_EQUAL(centipede_tail.stat, DEAD, "Centipede tail failed to die with head.") diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 312405d7382..ec0d97743ce 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -104,8 +104,6 @@ /mob/living/simple_animal/hostile/guardian/standard, /mob/living/simple_animal/hostile/guardian/support, /mob/living/simple_animal/hostile/heretic_summon, - /mob/living/simple_animal/hostile/heretic_summon/armsy, - /mob/living/simple_animal/hostile/heretic_summon/armsy/prime, /mob/living/simple_animal/hostile/heretic_summon/ash_spirit, /mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror, /mob/living/simple_animal/hostile/heretic_summon/rust_spirit, diff --git a/tgstation.dme b/tgstation.dme index a3b3f8de3d9..54bbfeb74f2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1119,6 +1119,7 @@ #include "code\datums\components\manual_heart.dm" #include "code\datums\components\mind_linker.dm" #include "code\datums\components\mirv.dm" +#include "code\datums\components\mob_chain.dm" #include "code\datums\components\mob_harvest.dm" #include "code\datums\components\multiple_lives.dm" #include "code\datums\components\mutant_hands.dm" @@ -4459,6 +4460,7 @@ #include "code\modules\mob\living\basic\farm_animals\cow\cow_moonicorn.dm" #include "code\modules\mob\living\basic\farm_animals\cow\cow_wisdom.dm" #include "code\modules\mob\living\basic\heretic\fire_shark.dm" +#include "code\modules\mob\living\basic\heretic\flesh_worm.dm" #include "code\modules\mob\living\basic\heretic\heretic_summon.dm" #include "code\modules\mob\living\basic\heretic\raw_prophet.dm" #include "code\modules\mob\living\basic\heretic\star_gazer.dm" From ea11a494478f0fb84cae7c66edf9494604d38164 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:44:13 +0200 Subject: [PATCH 09/78] Adds sanity checking to the tippable component [MDB IGNORE] (#24140) * Adds sanity checking to the tippable component (#78771) ## About The Pull Request The tippable component doesn't actually have any sanity checking for if the tippable thing has already been tipped. This means that if two people try to tip something over at the same time, it will appear untipped but be immobilized as though it were tipped. The sprite will also stay permanently flipped until the bug is preformed again. Now that doesn't happen. Closes #64232. ## Why It's Good For The Game As funny as it is to see upside-down borgs running around, its still a bug. ## Changelog :cl: fix: Borgs will no longer become permanently upside-down if tipped over by multiple people at the same time. /:cl: * Adds sanity checking to the tippable component --------- Co-authored-by: GPeckman <21979502+GPeckman@users.noreply.github.com> --- code/datums/components/tippable.dm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/datums/components/tippable.dm b/code/datums/components/tippable.dm index eafb2345818..ee1f2c40547 100644 --- a/code/datums/components/tippable.dm +++ b/code/datums/components/tippable.dm @@ -133,6 +133,8 @@ /datum/component/tippable/proc/do_tip(mob/living/tipped_mob, mob/tipper) if(QDELETED(tipped_mob)) CRASH("Tippable component: do_tip() called with QDELETED tipped_mob!") + if (is_tipped) // sanity check in case multiple people try to tip at the same time + return to_chat(tipper, span_warning("You tip over [tipped_mob].")) if (!isnull(tipped_mob.client)) @@ -185,6 +187,8 @@ /datum/component/tippable/proc/do_untip(mob/living/tipped_mob, mob/untipper) if(QDELETED(tipped_mob)) return + if (!is_tipped) // sanity check in case multiple people try to untip at the same time + return to_chat(untipper, span_notice("You right [tipped_mob].")) tipped_mob.visible_message( From e92f564f0c69639f9060032e2fc6fba4ec144c89 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:44:28 +0200 Subject: [PATCH 10/78] The AI can no longer turn off shapeshifted robots [MDB IGNORE] (#24141) * The AI can no longer turn off shapeshifted robots (#78766) ## About The Pull Request If you shapeshifted into a robot (beepsky, ED-209, etc.) using the shapechange spell or other means, silicons could just turn you off. Now all shapeshifted robots are considered to be emagged, meaning that silicons can't interact with them. Closes #44505. ## Why It's Good For The Game Bugfixes good ## Changelog :cl: fix: The AI can no longer turn you off if you shapeshift into a robot. /:cl: * The AI can no longer turn off shapeshifted robots --------- Co-authored-by: GPeckman <21979502+GPeckman@users.noreply.github.com> --- code/modules/spells/spell_types/shapeshift/_shapeshift.dm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm index 8acd6ca9247..a5e590685c0 100644 --- a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -159,6 +159,12 @@ spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB) ADD_TRAIT(new_shape, TRAIT_DONT_WRITE_MEMORY, SHAPESHIFT_TRAIT) // If you shapeshift into a pet subtype we don't want to update Poly's deathcount or something when you die + // Make sure that if you shapechanged into a bot, the AI can't just turn you off. + var/mob/living/simple_animal/bot/polymorph_bot = new_shape + if (istype(polymorph_bot)) + polymorph_bot.bot_cover_flags |= BOT_COVER_EMAGGED + polymorph_bot.bot_mode_flags &= ~BOT_MODE_REMOTE_ENABLED + return new_shape /// Actually does the un-shapeshift, from the caster. (Caster is a shapeshifted mob.) From 2747b50888297d5fa74a5392c96374e16bdc3459 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:44:50 +0200 Subject: [PATCH 11/78] QoL improvements to the mob damage unit tests [MDB IGNORE] (#24144) * QoL improvements to the mob damage unit tests (#78748) ## About The Pull Request This is something that I meant to do because it was a minor annoyance as I was creating the tests but I never got around to it. Because many of the failures occurred in procs it could be difficult to pinpoint the exact line of the test where they were failing. It would just be the line within the proc, and not the line where the proc was called. So you'd have to sort of infer which one it was from the values of `x` and `y` in `Expected x to be equal to y`. Now each test failure will have a brief description and a line number where `apply_damage()` / `verify_damage()` actually got called to make it clearer. Like shown below. ![Code_r6N3XSAb3m](https://github.com/tgstation/tgstation/assets/13398309/92ac5a91-3c3f-4b3b-90de-09fe0d9891f1) ## Why It's Good For The Game Just a small QoL update for coders. Some typos fixed, too. ## Changelog Nothing player facing though. * QoL improvements to the mob damage unit tests --------- Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> --- code/modules/unit_tests/mob_damage.dm | 237 +++++++++++++++++--------- 1 file changed, 160 insertions(+), 77 deletions(-) diff --git a/code/modules/unit_tests/mob_damage.dm b/code/modules/unit_tests/mob_damage.dm index 3d257e0f775..50046141a88 100644 --- a/code/modules/unit_tests/mob_damage.dm +++ b/code/modules/unit_tests/mob_damage.dm @@ -37,6 +37,56 @@ // testing with godmode enabled test_godmode(dummy) +/** + * Test whether the adjust damage procs return the correct values and that the mob's health is the expected value afterwards. + * + * By default this calls apply_damage(amount) followed by verify_damage(amount_after) and returns TRUE if both succeeded. + * amount_after defaults to the mob's current stamina loss but can be overridden as needed. + * + * Arguments: + * * testing_mob - the mob to apply the damage to + * * amount - the amount of damage to apply to the mob + * * expected - what the expected return value of the damage proc is + * * amount_after - in case you want to specify what the damage amount on the mob should be afterwards + * * included_types - Bitflag of damage types to apply + * * biotypes - the biotypes of damage to apply + * * bodytypes - the bodytypes of damage to apply + * * forced - whether or not this is forced damage + */ +/datum/unit_test/mob_damage/proc/test_apply_damage(mob/living/testing_mob, amount, expected = -amount, amount_after, included_types, biotypes, bodytypes, forced) + if(isnull(amount_after)) + amount_after = testing_mob.getStaminaLoss() - expected // stamina loss applies to both carbon and basic mobs the same way, so that's why we're using it here + if(!apply_damage(testing_mob, amount, expected, included_types, biotypes, bodytypes, forced)) + return FALSE + if(!verify_damage(testing_mob, amount_after, included_types)) + return FALSE + return TRUE + +/** + * Test whether the set damage procs return the correct values and that the mob's health is the expected value afterwards. + * + * By default this calls set_damage(amount) followed by verify_damage(amount_after) and returns TRUE if both succeeded. + * amount_after defaults to the mob's current stamina loss but can be overridden as needed. + * + * Arguments: + * * testing_mob - the mob to apply the damage to + * * amount - the amount of damage to apply to the mob + * * expected - what the expected return value of the damage proc is + * * amount_after - in case you want to specify what the damage amount on the mob should be afterwards + * * included_types - Bitflag of damage types to apply + * * biotypes - the biotypes of damage to apply + * * bodytypes - the bodytypes of damage to apply + * * forced - whether or not this is forced damage + */ +/datum/unit_test/mob_damage/proc/test_set_damage(mob/living/testing_mob, amount, expected, amount_after, included_types, biotypes, bodytypes, forced) + if(isnull(amount_after)) + amount_after = testing_mob.getStaminaLoss() - expected + if(!set_damage(testing_mob, amount, expected, included_types, biotypes, bodytypes, forced)) + return FALSE + if(!verify_damage(testing_mob, amount_after, included_types)) + return FALSE + return TRUE + /** * Check that the mob has a specific amount of damage * @@ -65,6 +115,7 @@ if(included_types & STAMINALOSS) TEST_ASSERT_EQUAL(testing_mob.getStaminaLoss(), amount, \ "[testing_mob] should have [amount] stamina damage, instead they have [testing_mob.getStaminaLoss()]!") + return TRUE /** * Apply a specific amount of damage to the mob using adjustBruteLoss(), adjustToxLoss(), etc. @@ -105,6 +156,7 @@ damage_returned = testing_mob.adjustStaminaLoss(amount, updating_stamina = FALSE, forced = forced, required_biotype = biotypes) TEST_ASSERT_EQUAL(damage_returned, expected, \ "adjustStaminaLoss() should have returned [expected], but returned [damage_returned] instead!") + return TRUE /** * Set a specific amount of damage for the mob using setBruteLoss(), setToxLoss(), etc. @@ -145,40 +197,43 @@ damage_returned = testing_mob.setStaminaLoss(amount, updating_stamina = FALSE, forced = forced, required_biotype = biotypes) TEST_ASSERT_EQUAL(damage_returned, expected, \ "setStaminaLoss() should have returned [expected], but returned [damage_returned] instead!") + return TRUE /// Sanity tests damage and healing using adjustToxLoss, adjustBruteLoss, etc /datum/unit_test/mob_damage/proc/test_sanity_simple(mob/living/carbon/human/consistent/dummy) - // Apply 5 damage and then heal it - apply_damage(dummy, 5) - verify_damage(dummy, 5) + if(!test_apply_damage(dummy, amount = 5)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly") - apply_damage(dummy, -5) - verify_damage(dummy, 0) + if(!test_apply_damage(dummy, amount = -5)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! healing was not applied correctly") // Apply 15 damage and heal 3 - apply_damage(dummy, 15) - verify_damage(dummy, 15) + if(!test_apply_damage(dummy, amount = 15)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly") - apply_damage(dummy, -3) - verify_damage(dummy, 12) + if(!test_apply_damage(dummy, amount = -3)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! underhealing was not applied correctly") // Now overheal by 666. It should heal for 12. - apply_damage(dummy, -666, expected = 12) - verify_damage(dummy, 0) + if(!test_apply_damage(dummy, amount = -666, expected = 12)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! overhealing was not applied correctly") // Now test the damage setter procs // set all types of damage to 5 - set_damage(dummy, 5, expected = -5) - verify_damage(dummy, 5) + if(!test_set_damage(dummy, amount = 5, expected = -5)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! failed to set damage to 5") // now try healing 5 - set_damage(dummy, 0, expected = 5) - verify_damage(dummy, 0) + if(!test_set_damage(dummy, amount = 0, expected = 5)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! failed to set damage to 0") /// Sanity tests damage and healing using the more complex procs like take_overall_damage(), heal_overall_damage(), etc /datum/unit_test/mob_damage/proc/test_sanity_complex(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + var/damage_returned // take 5 brute, 2 burn damage_returned = round(dummy.take_bodypart_damage(5, 2, updating_health = FALSE), 1) @@ -195,21 +250,24 @@ TEST_ASSERT_EQUAL(damage_returned, 5, \ "heal_bodypart_damage() should have returned 5, but returned [damage_returned] instead!") - verify_damage(dummy, 1, included_types = BRUTELOSS|FIRELOSS) + if(!verify_damage(dummy, 1, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("heal_bodypart_damage did not apply its healing correctly on the mob!") // heal 1 brute, 1 burn damage_returned = round(dummy.heal_overall_damage(1, 1, updating_health = FALSE), 1) TEST_ASSERT_EQUAL(damage_returned, 2, \ "heal_overall_damage() should have returned 2, but returned [damage_returned] instead!") - verify_damage(dummy, 0, included_types = BRUTELOSS|FIRELOSS) + if(!verify_damage(dummy, 0, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mob!") // take 50 brute, 50 burn damage_returned = round(dummy.take_overall_damage(50, 50, updating_health = FALSE), 1) TEST_ASSERT_EQUAL(damage_returned, -100, \ "take_overall_damage() should have returned -100, but returned [damage_returned] instead!") - verify_damage(dummy, 50, included_types = BRUTELOSS|FIRELOSS) + if(!verify_damage(dummy, 50, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("take_overall_damage did not apply its damage correctly on the mob!") // testing negative damage amount args with the overall damage procs - the sign should be ignored for these procs @@ -229,7 +287,8 @@ TEST_ASSERT_EQUAL(damage_returned, 10, \ "heal_overall_damage() should have returned 10, but returned [damage_returned] instead!") - verify_damage(dummy, 50, included_types = BRUTELOSS|FIRELOSS) + if(!verify_damage(dummy, 50, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healingcorrectly on the mob!") // testing overhealing @@ -237,134 +296,150 @@ TEST_ASSERT_EQUAL(damage_returned, 100, \ "heal_overall_damage() should have returned 100, but returned [damage_returned] instead!") - verify_damage(dummy, 0, included_types = BRUTELOSS|FIRELOSS) + if(!verify_damage(dummy, 0, included_types = BRUTELOSS|FIRELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mob!") /// Tests damage procs with godmode on /datum/unit_test/mob_damage/proc/test_godmode(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) // flip godmode bit to 1 dummy.status_flags ^= GODMODE // Apply 9 damage and then heal it - apply_damage(dummy, 9, expected = 0) - verify_damage(dummy, 0) + if(!test_apply_damage(dummy, amount = 9, expected = 0)) + TEST_FAIL("ABOVE FAILURE: failed test_godmode! mob took damage despite having godmode enabled.") - apply_damage(dummy, -9, expected = 0) - verify_damage(dummy, 0) + if(!test_apply_damage(dummy, amount = -9, expected = 0)) + TEST_FAIL("ABOVE FAILURE: failed test_godmode! mob healed when they should've been at full health.") // Apply 11 damage and then heal it, this time with forced enabled. The damage should go through regardless of godmode. - apply_damage(dummy, 11, forced = TRUE) - verify_damage(dummy, 11) + if(!test_apply_damage(dummy, amount = 11, forced = TRUE)) + TEST_FAIL("ABOVE FAILURE: failed test_godmode! godmode did not respect forced = TRUE") - apply_damage(dummy, -11, forced = TRUE) - verify_damage(dummy, 0) + if(!test_apply_damage(dummy, amount = -11, forced = TRUE)) + TEST_FAIL("ABOVE FAILURE: failed test_godmode! godmode did not respect forced = TRUE") // flip godmode bit back to 0 dummy.status_flags ^= GODMODE /// Testing biotypes /datum/unit_test/mob_damage/proc/test_biotypes(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) // Testing biotypes using a plasmaman, who is MOB_MINERAL and MOB_HUMANOID dummy.set_species(/datum/species/plasmaman) // argumentless default: should default to required_biotype = ALL. The damage should be applied in that case. - apply_damage(dummy, 1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS) - verify_damage(dummy, 1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS) + if(!test_apply_damage(dummy, 1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS)) + TEST_FAIL("ABOVE FAILURE: plasmaman did not take damage with biotypes = ALL") // If we specify MOB_ORGANIC, the damage should not get applied because plasmamen lack that biotype. - apply_damage(dummy, 1, expected = 0, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_ORGANIC) - verify_damage(dummy, 1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS) + if(!test_apply_damage(dummy, 1, expected = 0, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_ORGANIC)) + TEST_FAIL("ABOVE FAILURE: plasmaman took damage with biotypes = MOB_ORGANIC") // Now if we specify MOB_MINERAL the damage should get applied. - apply_damage(dummy, 1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_MINERAL) - verify_damage(dummy, 2, included_types = TOXLOSS|CLONELOSS|STAMINALOSS) + if(!test_apply_damage(dummy, 1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_MINERAL)) + TEST_FAIL("ABOVE FAILURE: plasmaman did not take damage with biotypes = MOB_MINERAL") // Transform back to human dummy.set_species(/datum/species/human) // We have 2 damage presently. // Try to heal it; let's specify MOB_MINERAL, which should no longer work because we have changed back to a human. - apply_damage(dummy, -2, expected = 0, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_MINERAL) - verify_damage(dummy, 2, included_types = TOXLOSS|CLONELOSS|STAMINALOSS) + if(!test_apply_damage(dummy, -2, expected = 0, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_MINERAL)) + TEST_FAIL("ABOVE FAILURE: human took damage with biotypes = MOB_MINERAL") // Force heal some of the damage. When forced = TRUE the damage/healing gets applied no matter what. - apply_damage(dummy, -1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_MINERAL, forced = TRUE) - verify_damage(dummy, 1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS) + if(!test_apply_damage(dummy, -1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_MINERAL, forced = TRUE)) + TEST_FAIL("ABOVE FAILURE: human did not get healed when biotypes = MOB_MINERAL and forced = TRUE") // Now heal the rest of it with the correct biotype. Make sure that this works. We should have 0 damage afterwards. - apply_damage(dummy, -1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_ORGANIC) - verify_damage(dummy, 0, included_types = TOXLOSS|CLONELOSS|STAMINALOSS) + if(!test_apply_damage(dummy, -1, included_types = TOXLOSS|CLONELOSS|STAMINALOSS, biotypes = MOB_ORGANIC)) + TEST_FAIL("ABOVE FAILURE: human did not get healed with biotypes = MOB_ORGANIC") /// Testing oxyloss with the TRAIT_NOBREATH /datum/unit_test/mob_damage/proc/test_nobreath(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + // TRAIT_NOBREATH is supposed to prevent oxyloss damage (but not healing). Let's make sure that's the case. ADD_TRAIT(dummy, TRAIT_NOBREATH, TRAIT_SOURCE_UNIT_TESTS) // force some oxyloss here dummy.setOxyLoss(2, updating_health = FALSE, forced = TRUE) // Try to take more oxyloss damage with TRAIT_NOBREATH. It should not work. - apply_damage(dummy, 2, expected = 0, included_types = OXYLOSS) - verify_damage(dummy, 2, included_types = OXYLOSS) + if(!test_apply_damage(dummy, 2, expected = 0, amount_after = dummy.getOxyLoss(), included_types = OXYLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_nobreath! mob took oxyloss damage while having TRAIT_NOBREATH") // Make sure we are still be able to heal the oxyloss. This should work. - apply_damage(dummy, -2, included_types = OXYLOSS) - verify_damage(dummy, 0, included_types = OXYLOSS) + if(!test_apply_damage(dummy, -2, amount_after = dummy.getOxyLoss()-2, included_types = OXYLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_nobreath! mob could not heal oxyloss damage while having TRAIT_NOBREATH") REMOVE_TRAIT(dummy, TRAIT_NOBREATH, TRAIT_SOURCE_UNIT_TESTS) /// Testing toxloss with TRAIT_TOXINLOVER and TRAIT_TOXIMMUNE /datum/unit_test/mob_damage/proc/test_toxintraits(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + // TRAIT_TOXINLOVER is supposed to invert toxin damage and healing. Things that would normally cause toxloss now heal it, and vice versa. ADD_TRAIT(dummy, TRAIT_TOXINLOVER, TRAIT_SOURCE_UNIT_TESTS) // force some toxloss here dummy.setToxLoss(2, updating_health = FALSE, forced = TRUE) // Try to take more toxloss damage with TRAIT_TOXINLOVER. It should heal instead. - apply_damage(dummy, 2, expected = 2, included_types = TOXLOSS) - verify_damage(dummy, 0, included_types = TOXLOSS) + if(!test_apply_damage(dummy, 2, expected = 2, amount_after = dummy.getToxLoss()-2, included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob did not heal from toxin damage with TRAIT_TOXINLOVER") // If we try to heal the toxloss we should take damage instead - apply_damage(dummy, -2, expected = -2, included_types = TOXLOSS) - verify_damage(dummy, 2, included_types = TOXLOSS) + if(!test_apply_damage(dummy, -2, expected = -2, amount_after = dummy.getToxLoss()+2, included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob did not take damage from toxin healing with TRAIT_TOXINLOVER") // TOXIMMUNE trait should prevent the damage you get from being healed by toxins medicines while having TRAIT_TOXINLOVER ADD_TRAIT(dummy, TRAIT_TOXIMMUNE, TRAIT_SOURCE_UNIT_TESTS) // need to force apply some toxin damage since the TOXIMUNNE trait sets toxloss to 0 upon being added - apply_damage(dummy, 2, included_types = TOXLOSS, forced = TRUE) + dummy.setToxLoss(2, updating_health = FALSE, forced = TRUE) // try to 'heal' again - this time it should just do nothing because we should be immune to any sort of toxin damage - including from inverted healing - apply_damage(dummy, -2, expected = 0, included_types = TOXLOSS) - verify_damage(dummy, 2, included_types = TOXLOSS) + if(!test_apply_damage(dummy, -2, expected = 0, amount_after = dummy.getToxLoss(), included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob should not have taken any damage or healing with TRAIT_TOXINLOVER + TRAIT_TOXIMMUNE") // ok, let's try taking 'damage'. The inverted damage should still heal mobs with the TOXIMMUNE trait. - apply_damage(dummy, 2, expected = 2, included_types = TOXLOSS) - verify_damage(dummy, 0, included_types = TOXLOSS) + if(!test_apply_damage(dummy, 2, expected = 2, amount_after = dummy.getToxLoss()-2, included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_toxintraits! mob did not heal from taking toxin damage with TRAIT_TOXINLOVER + TRAIT_TOXIMMUNE") REMOVE_TRAIT(dummy, TRAIT_TOXINLOVER, TRAIT_SOURCE_UNIT_TESTS) REMOVE_TRAIT(dummy, TRAIT_TOXIMMUNE, TRAIT_SOURCE_UNIT_TESTS) /// Testing cloneloss with TRAIT_NOCLONELOSS /datum/unit_test/mob_damage/proc/test_nocloneloss(mob/living/carbon/human/consistent/dummy) - // TRAIT_NOBREATH is supposed to prevent cloneloss damage and healing. Let's make sure that's the case. + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) + + // TRAIT_TRAIT_NOCLONELOSS is supposed to prevent cloneloss damage and healing. Let's make sure that's the case. ADD_TRAIT(dummy, TRAIT_NOCLONELOSS, TRAIT_SOURCE_UNIT_TESTS) // force some cloneloss here dummy.setCloneLoss(2, updating_health = FALSE, forced = TRUE) // Try to take more cloneloss damage with TRAIT_NOCLONELOSS. It should not work. - apply_damage(dummy, 2, expected = 0, included_types = CLONELOSS) - verify_damage(dummy, 2, included_types = CLONELOSS) + if(!test_apply_damage(dummy, 2, expected = 0, amount_after = dummy.getCloneLoss(), included_types = CLONELOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_nocloneloss! mob took cloneloss damage with TRAIT_NOCLONELOSS") // Healing the cloneloss should not work either, unless we force it - apply_damage(dummy, -2, expected = 0, included_types = CLONELOSS) - verify_damage(dummy, 2, included_types = CLONELOSS) + if(!test_apply_damage(dummy, -2, expected = 0, amount_after = dummy.getCloneLoss(), included_types = CLONELOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_nocloneloss! mob healed cloneloss damage with TRAIT_NOCLONELOSS") // so let's force it - apply_damage(dummy, -2, expected = 2, included_types = CLONELOSS, forced = TRUE) - verify_damage(dummy, 0, included_types = CLONELOSS) + if(!test_apply_damage(dummy, -2, expected = 2, amount_after = dummy.getCloneLoss()-2, included_types = CLONELOSS, forced = TRUE)) + TEST_FAIL("ABOVE FAILURE: failed test_nocloneloss! mob could not heal cloneloss damage with forced = TRUE and TRAIT_NOCLONELOSS") REMOVE_TRAIT(dummy, TRAIT_NOCLONELOSS, TRAIT_SOURCE_UNIT_TESTS) /// Testing heal_ordered_damage() /datum/unit_test/mob_damage/proc/test_ordered_healing(mob/living/carbon/human/consistent/dummy) + // Heal up, so that errors from the previous tests we won't cause this one to fail + dummy.fully_heal(HEAL_DAMAGE) var/damage_returned // We apply 20 brute, 20 burn, and 20 toxin damage. 60 damage total @@ -381,12 +456,12 @@ TEST_ASSERT_EQUAL(dummy.getFireLoss(), 10, \ "[src] should have 10 burn damage, but has [dummy.getFireLoss()] instead!") TEST_ASSERT_EQUAL(dummy.getToxLoss(), 20, \ - "[src] should have 2 toxin damage, but has [dummy.getToxLoss()] instead!") + "[src] should have 20 toxin damage, but has [dummy.getToxLoss()] instead!") // Now heal the remaining 30, overhealing by 5. damage_returned = round(dummy.heal_ordered_damage(35, list(BRUTE, BURN, TOX)), 1) TEST_ASSERT_EQUAL(damage_returned, 30, \ - "heal_ordered_damage() should have returned 0, but returned [damage_returned] instead!") + "heal_ordered_damage() should have returned 30, but returned [damage_returned] instead!") // Should have no damage remaining TEST_ASSERT_EQUAL(dummy.getBruteLoss(), 0, \ @@ -439,37 +514,42 @@ if(included_types & STAMINALOSS) TEST_ASSERT_EQUAL(testing_mob.getStaminaLoss(), amount, \ "[testing_mob] should have [amount] stamina damage, instead they have [testing_mob.getStaminaLoss()]!") + return TRUE /datum/unit_test/mob_damage/basic/test_sanity_simple(mob/living/basic/mouse/gray/gusgus) // check to see if basic mob damage works // Simple damage and healing // Take 1 damage, heal for 1 - apply_damage(gusgus, 1) - verify_damage(gusgus, 1) + if(!test_apply_damage(gusgus, amount = 1)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly") - apply_damage(gusgus, -1) - verify_damage(gusgus, 0) + if(!test_apply_damage(gusgus, amount = -1)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! healing was not applied correctly") // Give 2 damage of every time (translates to 10 brute, 2 staminaloss) - apply_damage(gusgus, 2) - verify_damage(gusgus, 2) + if(!test_apply_damage(gusgus, amount = 2)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! damage was not applied correctly") // underhealing: heal 1 damage of every type (translates to 5 brute, 1 staminaloss) - apply_damage(gusgus, -1) - verify_damage(gusgus, 1) + if(!test_apply_damage(gusgus, amount = -1)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! healing was not applied correctly") // overhealing // heal 11 points of toxloss (should take care of all 5 brute damage remaining) - apply_damage(gusgus, -11, expected = 5, included_types = TOXLOSS) + if(!apply_damage(gusgus, -11, expected = 5, included_types = TOXLOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! toxloss was not applied correctly") // heal the remaining point of staminaloss - apply_damage(gusgus, -11, expected = 1, included_types = STAMINALOSS) + if(!apply_damage(gusgus, -11, expected = 1, included_types = STAMINALOSS)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! failed to heal staminaloss correctly") // heal 35 points of each type, we should already be at full health so nothing should happen - apply_damage(gusgus, -35, expected = 0) - verify_damage(gusgus, 0) + if(!test_apply_damage(gusgus, amount = -35, expected = 0)) + TEST_FAIL("ABOVE FAILURE: failed test_sanity_simple! overhealing was not applied correctly") /datum/unit_test/mob_damage/basic/test_sanity_complex(mob/living/basic/mouse/gray/gusgus) + // Heal up, so that errors from the previous tests we won't cause this one to fail + gusgus.fully_heal(HEAL_DAMAGE) var/damage_returned // overall damage procs @@ -508,7 +588,8 @@ TEST_ASSERT_EQUAL(damage_returned, -6, \ "take_overall_damage() should have returned -6, but returned [damage_returned] instead!") - verify_damage(gusgus, 1, expected = 6, included_types = BRUTELOSS) + if(!verify_damage(gusgus, 1, expected = 6, included_types = BRUTELOSS)) + TEST_FAIL("take_overall_damage did not apply its damage correctly on the mouse!") // testing negative args with the overall damage procs @@ -528,7 +609,8 @@ TEST_ASSERT_EQUAL(damage_returned, 2, \ "heal_overall_damage() should have returned 2, but returned [damage_returned] instead!") - verify_damage(gusgus, 1, expected = 6, included_types = BRUTELOSS) + if(!verify_damage(gusgus, 1, expected = 6, included_types = BRUTELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mouse!") // testing overhealing @@ -536,4 +618,5 @@ TEST_ASSERT_EQUAL(damage_returned, 6, \ "heal_overall_damage() should have returned 6, but returned [damage_returned] instead!") - verify_damage(gusgus, 0, included_types = BRUTELOSS) + if(!verify_damage(gusgus, 0, included_types = BRUTELOSS)) + TEST_FAIL("heal_overall_damage did not apply its healing correctly on the mouse!") From 03736880157493017426e71f88ad44b671cbd87a Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:45:11 +0200 Subject: [PATCH 12/78] Bad food has bad food reagent again [MDB IGNORE] (#24145) * Bad food has bad food reagent again (#78747) still on break so i didn't think too hard about this one ## About The Pull Request Prevents food interactions from clearing the resulting food's reagents if the result is a bad recipe. Blackbox logging is also iffy here, some of them log it even if the resulting food is a burned mess, some don't. It's weird! ## Why It's Good For The Game Fixes #78400

Makes one car moth happy ![no habla espanol](https://github.com/tgstation/tgstation/assets/75863639/a3f26acb-3a7c-44a6-a4de-2b67e4d56230)
Did you know the appendix has bad_food reagent in it? https://github.com/tgstation/tgstation/blob/699d09ca33e807f9100689c3ab5fbec3916186ee/code/modules/surgery/organs/internal/appendix/_appendix.dm#L5-L15 ## Changelog :cl: fix: fixed bad food not having bad food reagents /:cl: * Bad food has bad food reagent again --------- Co-authored-by: Sealed101 --- code/datums/components/bakeable.dm | 2 +- code/datums/components/grillable.dm | 2 +- code/datums/elements/food/microwavable.dm | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/code/datums/components/bakeable.dm b/code/datums/components/bakeable.dm index 537ae62ddbe..1b56a0f9bd4 100644 --- a/code/datums/components/bakeable.dm +++ b/code/datums/components/bakeable.dm @@ -67,7 +67,7 @@ var/atom/original_object = parent var/obj/item/plate/oven_tray/used_tray = original_object.loc var/atom/baked_result = new bake_result(used_tray) - if(baked_result.reagents) //make space and tranfer reagents if it has any + if(baked_result.reagents && positive_result) //make space and tranfer reagents if it has any & the resulting item isn't bad food or other bad baking result baked_result.reagents.clear_reagents() original_object.reagents.trans_to(baked_result, original_object.reagents.total_volume) diff --git a/code/datums/components/grillable.dm b/code/datums/components/grillable.dm index f1fe80fe9fb..4bf43939793 100644 --- a/code/datums/components/grillable.dm +++ b/code/datums/components/grillable.dm @@ -108,7 +108,7 @@ if(original_object.custom_materials) grilled_result.set_custom_materials(original_object.custom_materials) - if(IsEdible(grilled_result)) + if(IsEdible(grilled_result) && positive_result) BLACKBOX_LOG_FOOD_MADE(grilled_result.type) grilled_result.reagents.clear_reagents() original_object.reagents?.trans_to(grilled_result, original_object.reagents.total_volume) diff --git a/code/datums/elements/food/microwavable.dm b/code/datums/elements/food/microwavable.dm index 3ad3e272d34..037e3359ac7 100644 --- a/code/datums/elements/food/microwavable.dm +++ b/code/datums/elements/food/microwavable.dm @@ -41,13 +41,13 @@ var/efficiency = istype(used_microwave) ? used_microwave.efficiency : 1 SEND_SIGNAL(result, COMSIG_ITEM_MICROWAVE_COOKED, source, efficiency) - if(IS_EDIBLE(result)) - if(microwaver && microwaver.mind) - ADD_TRAIT(result, TRAIT_FOOD_CHEF_MADE, REF(microwaver.mind)) + if(IS_EDIBLE(result) && (result_typepath != default_typepath)) + BLACKBOX_LOG_FOOD_MADE(result.type) result.reagents.clear_reagents() source.reagents?.trans_to(result, source.reagents.total_volume) - BLACKBOX_LOG_FOOD_MADE(result.type) + if(microwaver && microwaver.mind) + ADD_TRAIT(result, TRAIT_FOOD_CHEF_MADE, REF(microwaver.mind)) qdel(source) From 2d767da0d651c800031143ba350a82f07d44f1f7 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:45:23 +0200 Subject: [PATCH 13/78] fish analyzers can also be used to perform fish scanning experiments. [MDB IGNORE] (#24146) * fish analyzers can also be used to perform fish scanning experiments. (#78745) ## About The Pull Request I recently read of someone being confused that they couldn't use them to perform fish-related experiments, so I thought it would be a good idea to add that. Also, I've converted the related misc supply pack to a goodie one, like other fishing supplies. ## Why It's Good For The Game It makes sense, and a fair amount of players doesn't even know experiments exists until they play the scientist role a couple times. ## Changelog :cl: add: Fish analyzers can now be used to perform fish scanning experiments. balance: They can now be singularly bought as a goodie pack for 125 cr each, instead of a crate of three for 500 cr. /:cl: * fish analyzers can also be used to perform fish scanning experiments. --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/__DEFINES/dcs/signals/signals_fish.dm | 3 +++ code/modules/cargo/goodies.dm | 6 ++++++ code/modules/cargo/packs/general.dm | 9 +-------- code/modules/fishing/aquarium/fish_analyzer.dm | 17 +++++++++++++++++ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_fish.dm b/code/__DEFINES/dcs/signals/signals_fish.dm index 90e580e9166..a40e731fc7b 100644 --- a/code/__DEFINES/dcs/signals/signals_fish.dm +++ b/code/__DEFINES/dcs/signals/signals_fish.dm @@ -31,3 +31,6 @@ /// Sent when the challenge is to be interrupted: (reason) #define COMSIG_FISHING_SOURCE_INTERRUPT_CHALLENGE "fishing_spot_interrupt_challenge" + +/// From /obj/item/fish_analyzer/proc/analyze_status: (fish, user) +#define COMSIG_FISH_ANALYZER_ANALYZE_STATUS "fish_analyzer_analyze_status" diff --git a/code/modules/cargo/goodies.dm b/code/modules/cargo/goodies.dm index a9e11a370e1..27a770005c1 100644 --- a/code/modules/cargo/goodies.dm +++ b/code/modules/cargo/goodies.dm @@ -313,3 +313,9 @@ desc = "A less cheap imported climbing hook. Absolutely no use outside of planetary stations." cost = PAYCHECK_CREW * 5 contains = list(/obj/item/climbing_hook) + +/datum/supply_pack/goody/fish_analyzer + name = "Fish Analyzer" + desc = "A single analyzer to monitor fish's status and traits with, in case you don't have the technology to print one." + cost = CARGO_CRATE_VALUE * 2.5 + contains = list(/obj/item/fish_analyzer) diff --git a/code/modules/cargo/packs/general.dm b/code/modules/cargo/packs/general.dm index 5bfcf01eb00..99f8972ccb8 100644 --- a/code/modules/cargo/packs/general.dm +++ b/code/modules/cargo/packs/general.dm @@ -68,13 +68,6 @@ contains = list(/obj/item/storage/fish_case/tiziran = 2) crate_name = "tiziran fish crate" -/datum/supply_pack/misc/fish_analyzers - name = "Fish Analyzers" - desc = "A pack containing three analyzers to monitor fish's status and traits with." - cost = CARGO_CRATE_VALUE * 2.5 - contains = list(/obj/item/fish_analyzer = 3) - crate_name = "fish analyzers crate" - /datum/supply_pack/misc/bicycle name = "Bicycle" desc = "Nanotrasen reminds all employees to never toy with powers outside their control." @@ -221,7 +214,7 @@ /obj/item/clothing/under/misc/burial = 2, ) crate_name = "religious supplies crate" - + /datum/supply_pack/misc/candles_bulk name = "Candle Box Crate" desc = "Keep your local chapel lit with three candle boxes!" diff --git a/code/modules/fishing/aquarium/fish_analyzer.dm b/code/modules/fishing/aquarium/fish_analyzer.dm index 8afe053a4d5..a83aa4296b6 100644 --- a/code/modules/fishing/aquarium/fish_analyzer.dm +++ b/code/modules/fishing/aquarium/fish_analyzer.dm @@ -33,6 +33,17 @@ case_color = rgb(rand(16, 255), rand(16, 255), rand(16, 255)) set_greyscale(colors = list(case_color)) . = ..() + + var/static/list/fishe_signals = list( + COMSIG_FISH_ANALYZER_ANALYZE_STATUS = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_handheld_experiment), + ) + AddComponent(/datum/component/experiment_handler, \ + config_mode = EXPERIMENT_CONFIG_ALTCLICK, \ + allowed_experiments = list(/datum/experiment/scanning/fish), \ + config_flags = EXPERIMENT_CONFIG_SILENT_FAIL|EXPERIMENT_CONFIG_IMMEDIATE_ACTION, \ + experiment_signals = fishe_signals, \ + ) + register_item_context() update_appearance() @@ -42,6 +53,10 @@ radial_choices = null return ..() +/obj/item/fish_analyzer/examine(mob/user) + . = ..() + . += span_notice("Alt-Click to access the Experiment Configuration UI") + /obj/item/fish_analyzer/update_icon_state() . = ..() icon_state = base_icon_state @@ -206,6 +221,8 @@ to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO) + SEND_SIGNAL(src, COMSIG_FISH_ANALYZER_ANALYZE_STATUS, fish, user) + /** * Called when a fish or a menu choice is left-clicked. * This returns the fish's progenitors, traits and their inheritability. From d6d15a5a269d83f65237d4217ffd2f7c5e08e889 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:45:52 +0200 Subject: [PATCH 14/78] Fixes silent catwalks over otherwise silent turfs (open space, chasms) [MDB IGNORE] (#24147) * [NO GBP] Fixes silent catwalks over otherwise silent turfs (open space, chasms) (#78742) ## About The Pull Request Exactly what it reads on the tin. ## Why It's Good For The Game Fixes #78646. ## Changelog :cl: fix: Fixed silent catwalks. /:cl: * [NO GBP] Fixes silent catwalks over otherwise silent turfs (open space, chasms) --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/datums/elements/footstep.dm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/code/datums/elements/footstep.dm b/code/datums/elements/footstep.dm index 6c55b595563..c9623c9ed4a 100644 --- a/code/datums/elements/footstep.dm +++ b/code/datums/elements/footstep.dm @@ -64,11 +64,12 @@ if(!istype(turf)) return - if(!turf.footstep || source.buckled || source.throwing || source.movement_type & (VENTCRAWLING | FLYING) || HAS_TRAIT(source, TRAIT_IMMOBILIZED) || CHECK_MOVE_LOOP_FLAGS(source, MOVEMENT_LOOP_OUTSIDE_CONTROL)) + if(source.buckled || source.throwing || source.movement_type & (VENTCRAWLING | FLYING) || HAS_TRAIT(source, TRAIT_IMMOBILIZED) || CHECK_MOVE_LOOP_FLAGS(source, MOVEMENT_LOOP_OUTSIDE_CONTROL)) return if(source.body_position == LYING_DOWN) //play crawling sound if we're lying - playsound(turf, 'sound/effects/footstep/crawl1.ogg', 15 * volume, falloff_distance = 1, vary = sound_vary) + if(turf.footstep) + playsound(turf, 'sound/effects/footstep/crawl1.ogg', 15 * volume, falloff_distance = 1, vary = sound_vary) return if(iscarbon(source)) @@ -92,6 +93,9 @@ . = list(FOOTSTEP_MOB_SHOE = turf.footstep, FOOTSTEP_MOB_BAREFOOT = turf.barefootstep, FOOTSTEP_MOB_HEAVY = turf.heavyfootstep, FOOTSTEP_MOB_CLAW = turf.clawfootstep, STEP_SOUND_PRIORITY = STEP_SOUND_NO_PRIORITY) SEND_SIGNAL(turf, COMSIG_TURF_PREPARE_STEP_SOUND, .) + //The turf has no footstep sound (e.g. open space) and none of the objects on that turf (e.g. catwalks) overrides it + if(isnull(turf.footstep)) + return null return . /datum/element/footstep/proc/play_simplestep(mob/living/source, atom/oldloc, direction, forced, list/old_locs, momentum_change) From 570ebe80f5286188c748f44958276ccf30f69126 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:46:06 +0200 Subject: [PATCH 15/78] Organ harvester file cleanup [MDB IGNORE] (#24148) * Organ harvester file cleanup (#78728) ## About The Pull Request Replaces every single/double letter variable I could find in `code\game\machinery\harvester.dm` with a more descriptive variable name. I hope, anyway. Also replaces a typecheck section with just writing a better loop. ## Why It's Good For The Game Uuuuuuh. Code.... readability.... and maintainability? I think? ## Changelog Not player-facing. * Organ harvester file cleanup --------- Co-authored-by: Vladin Heir <44104681+VladinXXV@users.noreply.github.com> --- code/game/machinery/harvester.dm | 49 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/code/game/machinery/harvester.dm b/code/game/machinery/harvester.dm index 63a48dcd976..1a16c00e42d 100644 --- a/code/game/machinery/harvester.dm +++ b/code/game/machinery/harvester.dm @@ -75,21 +75,18 @@ /obj/machinery/harvester/proc/can_harvest() if(!powered() || state_open || !occupant || !iscarbon(occupant)) return - var/mob/living/carbon/C = occupant + var/mob/living/carbon/carbon_occupant = occupant if(!allow_clothing) - for(var/A in C.held_items + C.get_equipped_items()) - if(!isitem(A)) - continue - var/obj/item/I = A - if(!(HAS_TRAIT(I, TRAIT_NODROP))) + for(var/obj/item/abiotic_item in carbon_occupant.held_items + carbon_occupant.get_equipped_items()) + if(!(HAS_TRAIT(abiotic_item, TRAIT_NODROP))) say("Subject may not have abiotic items on.") playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) return - if(!(C.mob_biotypes & MOB_ORGANIC)) + if(!(carbon_occupant.mob_biotypes & MOB_ORGANIC)) say("Subject is not organic.") playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) return - if(!allow_living && !(C.stat == DEAD || HAS_TRAIT(C, TRAIT_FAKEDEATH))) //I mean, the machines scanners arent advanced enough to tell you're alive + if(!allow_living && !(carbon_occupant.stat == DEAD || HAS_TRAIT(carbon_occupant, TRAIT_FAKEDEATH))) //I mean, the machines scanners arent advanced enough to tell you're alive say("Subject is still alive.") playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) return @@ -124,21 +121,21 @@ end_harvesting(success = FALSE) return playsound(src, 'sound/machines/juicer.ogg', 20, TRUE) - var/mob/living/carbon/C = occupant + var/mob/living/carbon/carbon_occupant = occupant if(!LAZYLEN(operation_order)) //The list is empty, so we're done here end_harvesting(success = TRUE) return var/turf/target = get_step(src, output_dir) - for(var/obj/item/bodypart/BP in operation_order) //first we do non-essential limbs - BP.drop_limb() - C.emote("scream") - if(BP.body_zone != "chest") - BP.forceMove(target) //Move the limbs right next to it, except chest, that's a weird one - BP.drop_organs() + for(var/obj/item/bodypart/limb_to_remove as anything in operation_order) //first we do non-essential limbs + limb_to_remove.drop_limb() + carbon_occupant.emote("scream") + if(limb_to_remove.body_zone != "chest") + limb_to_remove.forceMove(target) //Move the limbs right next to it, except chest, that's a weird one + limb_to_remove.drop_organs() else - for(var/obj/item/organ/O in BP.dismember()) - O.forceMove(target) //Some organs, like chest ones, are different so we need to manually move them - operation_order.Remove(BP) + for(var/obj/item/organ/organ_to_remove in limb_to_remove.dismember()) + organ_to_remove.forceMove(target) //Some organs, like chest ones, are different so we need to manually move them + operation_order.Remove(limb_to_remove) break use_power(active_power_usage) addtimer(CALLBACK(src, PROC_REF(harvest)), interval) @@ -154,7 +151,7 @@ say("Subject has been successfully harvested.") playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, FALSE) -/obj/machinery/harvester/screwdriver_act(mob/living/user, obj/item/I) +/obj/machinery/harvester/screwdriver_act(mob/living/user, obj/item/tool) . = TRUE if(..()) return @@ -164,20 +161,20 @@ if(state_open) to_chat(user, span_warning("[src] must be closed to [panel_open ? "close" : "open"] its maintenance hatch!")) return - if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I)) + if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), tool)) return return FALSE -/obj/machinery/harvester/crowbar_act(mob/living/user, obj/item/I) - if(default_pry_open(I)) +/obj/machinery/harvester/crowbar_act(mob/living/user, obj/item/tool) + if(default_pry_open(tool)) return TRUE - if(default_deconstruction_crowbar(I)) + if(default_deconstruction_crowbar(tool)) return TRUE -/obj/machinery/harvester/default_pry_open(obj/item/I) //wew - . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR //We removed is_operational here +/obj/machinery/harvester/default_pry_open(obj/item/tool) //wew + . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && tool.tool_behaviour == TOOL_CROWBAR //We removed is_operational here if(.) - I.play_tool_sound(src, 50) + tool.play_tool_sound(src, 50) visible_message(span_notice("[usr] pries open \the [src]."), span_notice("You pry open [src].")) open_machine() From 42fdfcee3ec8ec18da3270a67fa4fc30fc171454 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:46:20 +0200 Subject: [PATCH 16/78] Makes the Regal Condor realistically simulate being shot dead with a high caliber hand cannon by making it HITSCAN [MDB IGNORE] (#24149) * Makes the Regal Condor realistically simulate being shot dead with a high caliber hand cannon by making it HITSCAN (#78674) ## About The Pull Request The Regal Condor come with a magazine and ammo already inside. The recipe for the magazine now no longer needs TC, but does need donk pockets (sponsored murder gear, you see) and a hell of a lot more materials per magazine (you're looking at like 40 sheets of various materials all up). It also needs you to make the Condor first. But it comes preloaded with ammo. The Condor is 1 whole TC more expensive. Also needs some metal. The old recipe is there in spirit. The Regal Condor and the magazines come with 10mm Reaper bullets. They're high damage. They're high AP. They are also hitscan. ## Why It's Good For The Game Apparently people don't like the Condor. Too much effort for not enough reward. After all, revolvers exist. 'It must be a joke' they say! 'It's joke content! I went to all that effort to make it for nothing! That slut Anne tricked us!' **Wrong, bitch.** If you want the Condor to make you shit yourself the moment someone with it appears on the screen, then fine! ### **You get what you fucking deserve.** ## Changelog :cl: balance: Despite earlier reports suggesting that the famous lethality of the Regal Condor was largely a myth, there has been rumors that the gun has once again started to display its true killing potential on any station that it 'manifests'. /:cl: * Makes the Regal Condor realistically simulate being shot dead with a high caliber hand cannon by making it HITSCAN --------- Co-authored-by: necromanceranne <40847847+necromanceranne@users.noreply.github.com> --- .../components/crafting/ranged_weapon.dm | 19 ++++++++++++------- .../ammunition/ballistic/pistol.dm | 5 +++++ .../boxes_magazines/external/pistol.dm | 8 ++------ .../projectiles/guns/ballistic/pistol.dm | 3 --- .../projectiles/projectile/bullets/pistol.dm | 19 +++++++++++++++++++ 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/code/datums/components/crafting/ranged_weapon.dm b/code/datums/components/crafting/ranged_weapon.dm index a8d631c6266..94a943b42c8 100644 --- a/code/datums/components/crafting/ranged_weapon.dm +++ b/code/datums/components/crafting/ranged_weapon.dm @@ -222,18 +222,19 @@ time = 30 SECONDS //contemplate for a bit category = CAT_WEAPON_RANGED -/datum/crafting_recipe/deagle_prime //When you factor in the makarov (7 tc), the toolbox (1 tc), and the emag (3 tc), this comes to a total of 17 TC or thereabouts. Igorning the 20k pricetag, obviously. +/datum/crafting_recipe/deagle_prime //When you factor in the makarov (7 tc), the toolbox (1 tc), and the emag (3 tc), this comes to a total of 18 TC or thereabouts. Igorning the 20k pricetag, obviously. name = "Regal Condor" always_available = FALSE - result = /obj/item/gun/ballistic/automatic/pistol/deagle/regal/no_mag + result = /obj/item/gun/ballistic/automatic/pistol/deagle/regal reqs = list( /obj/item/gun/ballistic/automatic/pistol = 1, /obj/item/stack/sheet/mineral/gold = 25, /obj/item/stack/sheet/mineral/silver = 25, /obj/item/food/donkpocket = 1, - /obj/item/stack/telecrystal = 3, + /obj/item/stack/telecrystal = 4, /obj/item/clothing/head/costume/crown/fancy = 1, //the captain's crown /obj/item/storage/toolbox/syndicate = 1, + /obj/item/stack/sheet/iron = 10, ) tool_behaviors = list(TOOL_SCREWDRIVER) tool_paths = list( @@ -249,18 +250,22 @@ blacklist += subtypesof(/obj/item/gun/ballistic/automatic/pistol) /datum/crafting_recipe/deagle_prime_mag - name = "Regal Condor Magazine (10mm)" + name = "Regal Condor Magazine (10mm Reaper)" always_available = FALSE - result = /obj/item/ammo_box/magazine/r10mm/empty + result = /obj/item/ammo_box/magazine/r10mm reqs = list( /obj/item/stack/sheet/iron = 10, - /obj/item/stack/telecrystal = 2, + /obj/item/stack/sheet/mineral/gold = 10, + /obj/item/stack/sheet/mineral/silver = 10, + /obj/item/stack/sheet/mineral/plasma = 10, + /obj/item/food/donkpocket = 1, //Station mass murder, as sponsored by Donk Co. ) tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WELDER) tool_paths = list( /obj/item/clothing/under/syndicate, /obj/item/clothing/mask/gas/syndicate, - /obj/item/card/emag + /obj/item/card/emag, + /obj/item/gun/ballistic/automatic/pistol/deagle/regal ) time = 5 SECONDS category = CAT_WEAPON_RANGED diff --git a/code/modules/projectiles/ammunition/ballistic/pistol.dm b/code/modules/projectiles/ammunition/ballistic/pistol.dm index c61888b9525..a2f55f797bd 100644 --- a/code/modules/projectiles/ammunition/ballistic/pistol.dm +++ b/code/modules/projectiles/ammunition/ballistic/pistol.dm @@ -21,6 +21,11 @@ desc = "A 10mm incendiary bullet casing." projectile_type = /obj/projectile/bullet/incendiary/c10mm +/obj/item/ammo_casing/c10mm/reaper + name = "10mm reaper bullet casing" + desc = "A 10mm reaper bullet casing." + projectile_type = /obj/projectile/bullet/c10mm/reaper + // 9mm (Makarov, Stechkin APS, PP-95) /obj/item/ammo_casing/c9mm diff --git a/code/modules/projectiles/boxes_magazines/external/pistol.dm b/code/modules/projectiles/boxes_magazines/external/pistol.dm index 49ea0029f8f..8b0bc1da7e5 100644 --- a/code/modules/projectiles/boxes_magazines/external/pistol.dm +++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm @@ -107,15 +107,11 @@ multiple_sprites = AMMO_BOX_PER_BULLET /obj/item/ammo_box/magazine/r10mm - name = "regal condor magazine (10mm)" + name = "regal condor magazine (10mm Reaper)" icon_state = "r10mm-8" base_icon_state = "r10mm" - ammo_type = /obj/item/ammo_casing/c10mm + ammo_type = /obj/item/ammo_casing/c10mm/reaper caliber = CALIBER_10MM max_ammo = 8 multiple_sprites = AMMO_BOX_PER_BULLET multiple_sprite_use_base = TRUE - -/obj/item/ammo_box/magazine/r10mm/empty - icon_state = "r10mm-0" - start_empty = TRUE diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm index 220df569ecb..f69494e0a18 100644 --- a/code/modules/projectiles/guns/ballistic/pistol.dm +++ b/code/modules/projectiles/guns/ballistic/pistol.dm @@ -99,9 +99,6 @@ actions_types = list(/datum/action/item_action/toggle_firemode) obj_flags = UNIQUE_RENAME // if you did the sidequest, you get the customization -/obj/item/gun/ballistic/automatic/pistol/deagle/regal/no_mag - spawnwithmagazine = FALSE - /obj/item/gun/ballistic/automatic/pistol/aps name = "\improper Stechkin APS machine pistol" desc = "A modernized reproduction of an old Soviet machine pistol. It fires quickly, but kicks like a mule. Uses 9mm ammo. Has a threaded barrel for suppressors." //SKYRAT EDIT diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm index ece260f92fb..8fccc510ff8 100644 --- a/code/modules/projectiles/projectile/bullets/pistol.dm +++ b/code/modules/projectiles/projectile/bullets/pistol.dm @@ -42,3 +42,22 @@ name = "10mm incendiary bullet" damage = 20 fire_stacks = 3 + +/obj/projectile/bullet/c10mm/reaper + name = "10mm reaper pellet" + damage = 50 + armour_penetration = 40 + tracer_type = /obj/effect/projectile/tracer/sniper + impact_type = /obj/effect/projectile/impact/sniper + muzzle_type = /obj/effect/projectile/muzzle/sniper + hitscan = TRUE + impact_effect_type = null + hitscan_light_intensity = 3 + hitscan_light_range = 0.75 + hitscan_light_color_override = LIGHT_COLOR_DIM_YELLOW + muzzle_flash_intensity = 5 + muzzle_flash_range = 1 + muzzle_flash_color_override = LIGHT_COLOR_DIM_YELLOW + impact_light_intensity = 5 + impact_light_range = 1 + impact_light_color_override = LIGHT_COLOR_DIM_YELLOW From 37d9db8589e9df5db2e28b5524434d038c1de090 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:46:35 +0200 Subject: [PATCH 17/78] Jungle-Generator Usability Fixes, Geode Patchups [MDB IGNORE] (#24150) * Jungle-Generator Usability Fixes, Geode Patchups (#78669) ## About The Pull Request Firstly; Patches some remaining chasm baseturfs on the Geode Pirate Shuttle by implementing a new subtype of jungle rocks and dark dirt that's safe for use in space. Secondly; makes the (unused here, but I'm making use of it elsewhere and I've been told someone else is, too) jungle generator have a lot less (possibly no) recursive atmosdiffs. Jungle mineral turfs now produce an open turf with the correct atmos composition, and jungle chasms are brought in-line as well. Thirdly; the redundant layer of grilles have been removed from the Geode, as they were being spawned already by the window spawner. Additionally rotates the geode's blast doors because they rotate now and it'd look weirder half the time otherwise. ~~On a related note: I really don't like the direction of the docking port being off of standard for everything that isn't an arrivals shuttle, but uhh. Catch me when I'm willing to deal with shuttlecode again, lmao.~~ ## Why It's Good For The Game Significantly lessens recursive atmospheric overhead if either the geode spawns *or* the jungle generator ever gets used here. ## Changelog :cl: fix: It is no longer possible to chasm yourself on the geode. Again. /:cl: * Jungle-Generator Usability Fixes, Geode Patchups --------- Co-authored-by: BluBerry016 <50649185+unit0016@users.noreply.github.com> --- _maps/shuttles/pirate_geode.dmm | 56 +++++++++++---------------------- code/game/turfs/open/chasm.dm | 1 - code/game/turfs/open/planet.dm | 22 +++++++++++-- 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/_maps/shuttles/pirate_geode.dmm b/_maps/shuttles/pirate_geode.dmm index d2e8f034f48..693b7c8635f 100644 --- a/_maps/shuttles/pirate_geode.dmm +++ b/_maps/shuttles/pirate_geode.dmm @@ -127,21 +127,19 @@ }, /turf/open/floor/plating, /area/shuttle/pirate) +"iG" = ( +/obj/effect/spawner/structure/window/hollow/survival_pod, +/obj/machinery/door/poddoor/shutters/preopen{ + id = "geodebridge"; + dir = 4 + }, +/turf/open/misc/dirt/station, +/area/shuttle/pirate) "jh" = ( /turf/open/floor/catwalk_floor, /area/shuttle/pirate) -"jt" = ( -/obj/machinery/power/terminal{ - dir = 8 - }, -/turf/closed/mineral/random/jungle{ - baseturfs = /turf/open/misc/dirt - }, -/area/shuttle/pirate) "jx" = ( -/turf/closed/mineral/random/jungle{ - baseturfs = /turf/open/misc/dirt - }, +/turf/closed/mineral/random/jungle/space_safe, /area/shuttle/pirate) "jD" = ( /obj/effect/turf_decal/lunar_sand/plating, @@ -465,7 +463,6 @@ /turf/open/floor/plating, /area/shuttle/pirate) "xg" = ( -/obj/structure/grille, /obj/effect/spawner/structure/window/hollow/survival_pod, /obj/structure/barricade/wooden/crude, /turf/open/misc/dirt/station, @@ -515,11 +512,6 @@ /obj/structure/reagent_dispensers/watertank, /turf/open/misc/dirt/station, /area/shuttle/pirate) -"At" = ( -/turf/closed/mineral/random/jungle{ - baseturfs = null - }, -/area/shuttle/pirate) "AT" = ( /obj/effect/turf_decal/lunar_sand/plating, /obj/docking_port/mobile/pirate{ @@ -864,14 +856,6 @@ }, /turf/open/misc/dirt/station, /area/shuttle/pirate) -"Qg" = ( -/obj/structure/grille, -/obj/effect/spawner/structure/window/hollow/survival_pod, -/obj/machinery/door/poddoor/shutters/preopen{ - id = "geodebridge" - }, -/turf/open/misc/dirt/station, -/area/shuttle/pirate) "Qo" = ( /turf/open/floor/iron/stairs{ dir = 1 @@ -948,7 +932,6 @@ /obj/effect/turf_decal/lunar_sand/plating, /obj/structure/barricade/wooden/crude, /obj/effect/spawner/structure/window/hollow/survival_pod, -/obj/structure/grille, /turf/open/floor/plating, /area/shuttle/pirate) "Un" = ( @@ -963,7 +946,6 @@ /turf/closed/wall/mineral/plastitanium/nodiagonal, /area/shuttle/pirate) "UM" = ( -/obj/structure/grille, /obj/effect/spawner/structure/window/hollow/survival_pod, /turf/open/misc/dirt/station, /area/shuttle/pirate) @@ -1050,11 +1032,11 @@ mv Od Pq Pq -At +jx Pq Pq Pq -At +jx Pq Pq Od @@ -1066,8 +1048,8 @@ lt (3,1,1) = {" lt lt -At -At +jx +jx Od Uf Uf @@ -1079,8 +1061,8 @@ Od Uf Uf Od -At -At +jx +jx lt lt "} @@ -1212,7 +1194,7 @@ jx "} (10,1,1) = {" lt -jt +jx Od DT ZF @@ -1407,9 +1389,9 @@ jx jx Od Od -Qg -Qg -Qg +iG +iG +iG Od Od Od diff --git a/code/game/turfs/open/chasm.dm b/code/game/turfs/open/chasm.dm index 4c8ac12202d..49f6663d097 100644 --- a/code/game/turfs/open/chasm.dm +++ b/code/game/turfs/open/chasm.dm @@ -105,7 +105,6 @@ icon = 'icons/turf/floors/junglechasm.dmi' icon_state = "junglechasm-255" base_icon_state = "junglechasm" - initial_gas_mix = OPENTURF_LOW_PRESSURE planetary_atmos = TRUE baseturfs = /turf/open/chasm/jungle diff --git a/code/game/turfs/open/planet.dm b/code/game/turfs/open/planet.dm index e5ab02c0924..65c76cef957 100644 --- a/code/game/turfs/open/planet.dm +++ b/code/game/turfs/open/planet.dm @@ -18,14 +18,27 @@ name = "dirt flooring" //FOR THE LOVE OF GOD USE THIS INSTEAD OF DIRT FOR STATION MAPS desc = "You heard this place was dirty, but this is just absurd." baseturfs = /turf/open/floor/plating - initial_gas_mix = OPENTURF_LOW_PRESSURE + initial_gas_mix = OPENTURF_DEFAULT_ATMOS planetary_atmos = FALSE +/turf/open/misc/dirt/jungle + slowdown = 0.5 + initial_gas_mix = OPENTURF_DEFAULT_ATMOS + /turf/open/misc/dirt/dark icon_state = "greenerdirt" base_icon_state = "greenerdirt" -/turf/open/misc/dirt/jungle +/turf/open/misc/dirt/dark/station + baseturfs = /turf/open/floor/plating + initial_gas_mix = OPENTURF_DEFAULT_ATMOS + planetary_atmos = FALSE + +/turf/open/misc/dirt/dark/station/airless + initial_gas_mix = AIRLESS_ATMOS + temperature = TCMB + +/turf/open/misc/dirt/dark/jungle slowdown = 0.5 initial_gas_mix = OPENTURF_DEFAULT_ATMOS @@ -68,7 +81,7 @@ return list("jungle_damaged") /turf/closed/mineral/random/jungle - baseturfs = /turf/open/misc/dirt/dark + baseturfs = /turf/open/misc/dirt/dark/jungle /turf/closed/mineral/random/jungle/mineral_chances() return list( @@ -81,3 +94,6 @@ /obj/item/stack/ore/titanium = 11, /obj/item/stack/ore/uranium = 5, ) + +/turf/closed/mineral/random/jungle/space_safe + baseturfs = /turf/open/misc/dirt/dark/station/airless From cb3a54fa7f874fa8c7dec4304098f6d453f37e4f Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:46:49 +0200 Subject: [PATCH 18/78] The laser carbine, a full-auto sidegrade to the normal laser gun [MDB IGNORE] (#24151) * The laser carbine, a full-auto sidegrade to the normal laser gun (#78685) ## About The Pull Request This PR adds the laser carbine, a new fully-automatic laser weapon that can be ordered from cargo. A crate of 3 can be ordered from cargo for 1800 credits, locked behind armory access. Here is a video demonstration: https://github.com/tgstation/tgstation/assets/21979502/6f7fecec-ccb6-4a65-8027-21ab887fb91d Now, I'm sure people are very concerned about the balance implications of this new weapon. Let me give you some hard numbers: The gun deals 10 damage per shot, and has a capacity of 40 shots with a fully charged cell. This means that it has, at most, 400 damage per charge, which is exactly the same as a normal laser gun. In terms of DPS, it can put an unarmored human in crit roughly as fast a laser gun. It is meant to be a sidegrade, not an upgrade to the normal laser gun. It also has considerably lower wound bonus. During testing, when all 40 shots were fired into an unarmored human, it dealt tier 1 burn wounds with the occasional tier 2. I never observed a single tier 3 burn wound during any of my tests. Here's a picture of the different sprites (The last one is animated just like the normal laser gun): ![laser_carbines](https://github.com/tgstation/tgstation/assets/21979502/75c88c8a-aa8a-481d-994d-86850fcdbb9b) ## Why It's Good For The Game For a long time, there has been a strong push to make crew-available weapons almost entirely energy based. This trend has been contentious, to say the least. Many people prefer ballistic weapons over energy weapons. After spending some time on a different codebase, one where autorifles are still completely available to order from cargo, no emag needed, I think I might know why (or at least part of the reason). Part of what I find satisfying about some ballistics is the fact that they fire quickly and automatically. Energy weapons might be more enjoyable to use if automatic energy weapons are also an option. ## Changelog :cl: add: The laser carbine, a weak but fully automatic sidegrade to the normal laser gun, can now be ordered from cargo. /:cl: --------- Co-authored-by: Jacquerel * The laser carbine, a full-auto sidegrade to the normal laser gun --------- Co-authored-by: GPeckman <21979502+GPeckman@users.noreply.github.com> Co-authored-by: Jacquerel --- code/datums/components/fullauto.dm | 21 +++++++++++------- code/modules/cargo/packs/security.dm | 8 +++++++ .../projectiles/ammunition/energy/laser.dm | 5 +++++ code/modules/projectiles/guns/energy/laser.dm | 10 +++++++++ code/modules/projectiles/projectile/beams.dm | 5 +++++ icons/obj/weapons/guns/energy.dmi | Bin 46351 -> 46988 bytes icons/obj/weapons/guns/projectiles.dmi | Bin 137026 -> 137134 bytes 7 files changed, 41 insertions(+), 8 deletions(-) diff --git a/code/datums/components/fullauto.dm b/code/datums/components/fullauto.dm index d02f090ae20..8663de5adc6 100644 --- a/code/datums/components/fullauto.dm +++ b/code/datums/components/fullauto.dm @@ -8,8 +8,12 @@ var/turf/target_loc //For dealing with locking on targets due to BYOND engine limitations (the mouse input only happening when mouse moves). var/autofire_stat = AUTOFIRE_STAT_IDLE var/mouse_parameters - var/autofire_shot_delay = 0.3 SECONDS //Time between individual shots. - var/mouse_status = AUTOFIRE_MOUSEUP //This seems hacky but there can be two MouseDown() without a MouseUp() in between if the user holds click and uses alt+tab, printscreen or similar. + /// Time between individual shots. + var/autofire_shot_delay = 0.3 SECONDS + /// This seems hacky but there can be two MouseDown() without a MouseUp() in between if the user holds click and uses alt+tab, printscreen or similar. + var/mouse_status = AUTOFIRE_MOUSEUP + /// Should dual wielding be allowed? + var/allow_akimbo ///windup autofire vars ///Whether the delay between shots increases over time, simulating a spooling weapon @@ -26,7 +30,7 @@ var/timerid COOLDOWN_DECLARE(next_shot_cd) -/datum/component/automatic_fire/Initialize(autofire_shot_delay, windup_autofire, windup_autofire_reduction_multiplier, windup_autofire_cap, windup_spindown) +/datum/component/automatic_fire/Initialize(autofire_shot_delay, windup_autofire, windup_autofire_reduction_multiplier, windup_autofire_cap, windup_spindown, allow_akimbo = TRUE) . = ..() if(!isgun(parent)) return COMPONENT_INCOMPATIBLE @@ -34,6 +38,7 @@ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(wake_up)) if(autofire_shot_delay) src.autofire_shot_delay = autofire_shot_delay + src.allow_akimbo = allow_akimbo if(windup_autofire) src.windup_autofire = windup_autofire src.windup_autofire_reduction_multiplier = windup_autofire_reduction_multiplier @@ -256,7 +261,7 @@ if(HAS_TRAIT(shooter, TRAIT_DOUBLE_TAP)) next_delay = round(next_delay * 0.5) COOLDOWN_START(src, next_shot_cd, next_delay) - if(SEND_SIGNAL(parent, COMSIG_AUTOFIRE_SHOT, target, shooter, mouse_parameters) & COMPONENT_AUTOFIRE_SHOT_SUCCESS) + if(SEND_SIGNAL(parent, COMSIG_AUTOFIRE_SHOT, target, shooter, allow_akimbo, mouse_parameters) & COMPONENT_AUTOFIRE_SHOT_SUCCESS) return TRUE stop_autofiring() return FALSE @@ -288,21 +293,21 @@ return COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS -/obj/item/gun/proc/do_autofire(datum/source, atom/target, mob/living/shooter, params) +/obj/item/gun/proc/do_autofire(datum/source, atom/target, mob/living/shooter, allow_akimbo, params) SIGNAL_HANDLER if(semicd || shooter.incapacitated()) return NONE if(!can_shoot()) shoot_with_empty_chamber(shooter) return NONE - INVOKE_ASYNC(src, PROC_REF(do_autofire_shot), source, target, shooter, params) + INVOKE_ASYNC(src, PROC_REF(do_autofire_shot), source, target, shooter, allow_akimbo, params) return COMPONENT_AUTOFIRE_SHOT_SUCCESS //All is well, we can continue shooting. -/obj/item/gun/proc/do_autofire_shot(datum/source, atom/target, mob/living/shooter, params) +/obj/item/gun/proc/do_autofire_shot(datum/source, atom/target, mob/living/shooter, allow_akimbo, params) var/obj/item/gun/akimbo_gun = shooter.get_inactive_held_item() var/bonus_spread = 0 - if(istype(akimbo_gun) && weapon_weight < WEAPON_MEDIUM) + if(istype(akimbo_gun) && weapon_weight < WEAPON_MEDIUM && allow_akimbo) if(akimbo_gun.weapon_weight < WEAPON_MEDIUM && akimbo_gun.can_trigger_gun(shooter)) bonus_spread = dual_wield_spread addtimer(CALLBACK(akimbo_gun, TYPE_PROC_REF(/obj/item/gun, process_fire), target, shooter, TRUE, params, null, bonus_spread), 1) diff --git a/code/modules/cargo/packs/security.dm b/code/modules/cargo/packs/security.dm index 7abe5f7601e..784b870fea0 100644 --- a/code/modules/cargo/packs/security.dm +++ b/code/modules/cargo/packs/security.dm @@ -225,6 +225,14 @@ crate_name = "energy gun crate" crate_type = /obj/structure/closet/crate/secure/plasma +/datum/supply_pack/security/armory/laser_carbine + name = "Laser Carbine Crate" + desc = "Contains three laser carbines, capable of rapidly firing weak lasers." + cost = CARGO_CRATE_VALUE * 9 + contains = list(/obj/item/gun/energy/laser/carbine = 3) + crate_name = "laser carbine crate" + crate_type = /obj/structure/closet/crate/secure/plasma + /datum/supply_pack/security/armory/exileimp name = "Exile Implants Crate" desc = "Contains five Exile implants." diff --git a/code/modules/projectiles/ammunition/energy/laser.dm b/code/modules/projectiles/ammunition/energy/laser.dm index fbf9b289aa0..4f62af047d4 100644 --- a/code/modules/projectiles/ammunition/energy/laser.dm +++ b/code/modules/projectiles/ammunition/energy/laser.dm @@ -16,6 +16,11 @@ e_cost = 62.5 select_name = "kill" +/obj/item/ammo_casing/energy/lasergun/carbine + projectile_type = /obj/projectile/beam/laser/carbine + e_cost = 25 // 40 shots + select_name = "kill" + /obj/item/ammo_casing/energy/lasergun/old projectile_type = /obj/projectile/beam/laser e_cost = 200 diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index 0409f2fe037..9ad095f67e0 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -37,6 +37,16 @@ cell_type = /obj/item/stock_parts/cell //SKYRAT EDIT ADDITION - GUNSGALORE ammo_x_offset = 3 +/obj/item/gun/energy/laser/carbine + name = "laser carbine" + desc = "A modified laser gun which can shoot far faster, but each shot is far less damaging." + icon_state = "laser_carbine" + ammo_type = list(/obj/item/ammo_casing/energy/lasergun/carbine) + +/obj/item/gun/energy/laser/carbine/Initialize(mapload) + . = ..() + AddComponent(/datum/component/automatic_fire, 0.15 SECONDS, allow_akimbo = FALSE) + /obj/item/gun/energy/laser/retro/old name ="laser gun" icon_state = "retro" diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index e5c4d520d0e..d6c47531c91 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -28,6 +28,11 @@ damage = 25 bare_wound_bonus = 40 +/obj/projectile/beam/laser/carbine + icon_state = "carbine_laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/yellow_laser + damage = 10 + //overclocked laser, does a bit more damage but has much higher wound power (-0 vs -20) /obj/projectile/beam/laser/hellfire name = "hellfire laser" diff --git a/icons/obj/weapons/guns/energy.dmi b/icons/obj/weapons/guns/energy.dmi index 97b75335b91a0e1a8cdc5b1d6302999111abba7d..e45c5ee4869ed72a73516b1c565620543c362584 100644 GIT binary patch delta 9108 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 delta 8471 zcmcI|XH=6<(|16M4Z(t_fS`Z~2&gnEAyhFSph%G#0i_B-dcRPbqJTt@-g}MG2~7p$ z522TUbVLb+5?V+|Nb-dHe!o1=Iq$jO^PKa3*t2`>%+Bu2?9T6+`G9AR$1#5tW-3k) zxyIy^pmPDAxd3aCr4Rz;Q#V4~&rNJ`tAw9fZ)R zo}%hQ>j&>#D5al18eOVSS`^%pWGT@utZ+`!m+wJ3?|-z3vwz7G_hNooQuyU{rh^_im z(4w0R(~oHf=7@&=%Wpl5r=jQfji#YqX@;~N@&qss(6%{`tCwPUv(?n(x_L9yI^9p+ zs$UGt7jd?TDk{W2bIloN3*b+(PFX0B{l3jy${w2w04OB$B zAX$8hyNMU@r+1EH*9%5Y-&+yy-^pL7W~tLO$Px5$OyySV-@U~p*3YTy>n-zK%1-iW zz&C)*B6{uf;Gm1{*?XERwstO&eO;!LEzbfat~8G9yV#Y2A3ydXvpm7o!KL36jKSxh zvRwY^>$9H`S@SXz?09*6?J7&tfKUz=|86D4E%NJ0f~WKiwpoxi>-x8Ic>@g>InHUo zL91Fb3GDiJ^AkPSx6iIDEC~$_ckASI4D#~=Gm}-rhdF@86X0QN+V^^V zExg`qSLJshs!u&aG@;eE{kynQ-+S1MazEg#Nw$@_QkxikiOXB>;Nsg0wZUzyC(pIA z9RP7BQXy74+4sGZ&Oj#EQ^b%1AWc{K)(i@RFnGZ-I4pH!?ex~xhFIpIQfxrwhhJs! z9_#?uoacdDKv#jWKt^=(-nna2h)1*gV>yw2!yXbjt;)LJ7xwJXxymu6G1uqVx&(A5 zk`OBJI&w0PX`~lb^zNqPI1xSQ9OP;oJv#C^#tOz-m2%<|5*KlAInF9vuT zR$PqDXAUZy>G)|7C1Gq4fo(a~WN9mHRyG_$-ie)jQ~iFLvCJJ}%; zO+M;5%bz8{gQ!LLmEj*){=wd`A2-}Z!p4d3yK@ik;aM%rBl(}{h)o*v5ejVnytU=% zIwfO#nnR(%aR_^Cp$sh;fkb8O-H`;+J8-YqdNi5nEd8 zH0Sk)*|w`tYkxWCm^Cp`)jH4V=N@4(V|A^=d~_mu*J0kII3%&DG^@3wIR4}Z1BsjnIQv;7D?IVV-5HkBRNt5x?zwo z-4@c^+uQ5Hw_L}$sw*c=`T_tL{4_O?1d$%iZ&Fs-2(oO?b}t+pSS_ipz6;+H+8W2) z;(2}AQuXOlb+_Ew&k*l9X7riYHE~ZZE=_0?vCVaxL!lg_L4l&Sjb%zACBK*ECs3sD z$ErC2lf>)h1r7|o31(7^=&iJzTJu~l==CPIg; zyt=@UI$>v2sJ&f$>sqUlKkP()u$mVchejIP)VKfC=A-2f_)UdA+;RSZot0z(aeqVf``q1cuWw!RkTM&>E5$%Anre#9~1E*m9rQ5lnP3BfsUY> ztHpev?icYjwN^JSEe^uwL#aM!(sYIBa=#3X$45n#9K5#ZoSmkT?RAVvHj};r)9zx@ z$OJSGvhX@9UZ0P#pYb2u{bVe2=;l;-qQPxw#6+)hY|L0_HL>l)^Y8S-fvI~wWli?a z1VYID*W4ug&4~LOFQQRq!S1w|0yO^YyHiHJPWZcQgG8(64h`qbC>YV-Qp^c1akO7% zz>HB{`z;O^LY{8j(^9+a)m1>WNF}gFFAQlkF;}WwVgVwlx_3cNs%dYqv(q1ma;DxiO(Yd$-tfe@`wPdFCGe zZ?aWIyy}5fGR*TU%2#pPf;iAUm$}yV#=35qzJ~@Q3aYI02t&279XeZ1RJW+U0zx)q zceeph69-fOL>Y09b{N+>6so&iG9Syvk z)6ftM3tQ((8ETJr=L-$Ls45SmBP~VU#Kn;{t@PYd4jbB!?&b5$LM+i?j{#}rKjY(h z0g`Eu+3JpYaC^Xm?4L8mo7Pi~*%j6ltOD;LlCFJ1{8h4CBczHox<3!xb>k7mDS&gy z!sA{^w2#%PGwBmSoH1|7H0p7)kmkZrC!Bzxf&6QWW`XZj0=<$U#dU0@~wyb!Rv>2VIiUFR2@Z8;^qg3(yt5O>a0e>9aoFPH5FmtL&wop8E)? zSS8EXQ=J0cH(hm=^$<3A@INXRa~;hxJK3s9FjHUZ;_TFYtq_178d2UhTfav}60aKL z4}Gc6zN{f7%ZzgN-vit_efw}J37O_0Ps8S~WKEIxn>lC-JR z39=z%Mr<(9U$>c!INA_uO+D@t@-HpmAm<)Jw4ZCawy5)YfKO;5LR$Z}=Ccs1Q`hY& z3bx!W>mBWK;wNdg=gZv)-;=aQR z^Q6n>pYa$DS#b6D)(Z3Xs?Z2j8vseOXjjLA_Orkp>lupOIB-x@LP4esHe#>i^Woz2 zPa$3B=g8@LQ){BuoX%d*AZ>c6!w?1G?f>}~Mhg5a1oj<=*4y-!X+u{8t^v_`(#NaL z+7}1XHP+_97OboKrN@@$5_@W3}2!rhyrT2)hKY5tQY zs^M|`(pxSfReaWJrZ`@=S6l=tvU#2Rg1`H;k=eJJY>As&D(wyj!RV{ndjKCI3FKjs zrXtpFu(V(PySq0G3K<~$g-ZXUb5wT@++T^PDMajRziF`z~7zGi+x_M}jl8cQSI zY_&*-cnLa^Af#h#WLkR8FTwA^mzv)Y? zK41t2m;XGo|1|)R71_MophPg(Vtj3Gv`bhLPFZ=j;VR2Gi>Aiwrq|*2}Fldw=4N2&oeRP%ezha3s9oeLC59i zsqyejTIg@^UmC7K_}9scHgptbdQ<22+H@N@@ZphEg<1carq!S6`pX5w4u_2wWd2O8^@BU2 zQm;+ez<)tb?hr(*n+O(_1{VY4Q^llhgfw{_?nXEtWrA-j{i36Z%!y znekyTl}*lqzrD<<>RvVV zB02`Qh9&%|l&Y~X8o3QMuLGthwVa>d|11LKziU;rb*gk0hboq-DM&EST8%pHM?G^n zi+`=&CujnyC0Z<=ZKebD^=&ntS?*AC!acF2cib&+gj0l zN?IXM^5_(h@;grheE08JAKci0vSGl1EqPcr&LUvIUzOm&5?D)I;f5+p8fl6{Ai=#FJ)Th>=6wZwKQR@spW?5NuaD_lsxwu&z(P?LsxPIhvQpYfdMiBJpTx5^0};Y!?bwE&V;a8gdjpp_j%EkqS@wl z7SficSozSMR1t?>IejUCMq0_cfvR>AF^fzzsFiiG^Iy|DITrnKEL*NTkU}9`D|{%o%0B`Gh?hK z%`TFze$zHCb&kzD10PJ|@x8J^e^EK{7R<%r>;hB)ue}FJSt-;<-v&FgF|>{&6{pO- zpH}LYLo7D{|5V}9V7#vnPEie!I8;};pJ*I_Epa(qmusJDyrAN6f zhZ>CCJtZXFVgh!0STP^de_nwqh1Tj;4!TP~aZ`oB``-8lLR;IDHE~JHjMC%@xRt8= z%EDK%^P5Ho+x1Fq0Q}RjXZFUi9;x-NZ-BCsgi=6h97*K5&2>^nhf_xM9GkxmzG`FG zdG;l@1J>#8{!qy4L2<3&b;2P|j1tgt*c(SIwfE-`iF#pt@qsh5q z_2M{yQQn}ZSsLNxx+RE##qmCGjSL2)xOvu17#+XzerB9ml>8xdq-x2C{^()>BW#D0 zKY*^UJH%aesJfb%bC{BSv&z3l*xAhH-Tnmu)49~)z}3HL*y+8J|!E2&53JO^7LQR zUgPe*0lav{V7?Jn4f{P(^9Ny8ujf)1r0$F#I|x*q9moHC->lcAlyH4^5$u=ZdqO%YIOtw!6|nF=CdSCcrR-Ic z)l|J(Is$=kspaP8mRpXEi8=YgkGInNK8Q!9rjsR*Py7gjOYy)Md66sbc73~%Y4;h= z&izV5{3OBlge^jLtM}~U&2olpi&p!I=yf_oQnFuIM8vr-<<1ouhYt+dElluV2z;$u zg-Lmm%J;#)o<}r9!HM%XQhGj#rXRIvfzU~c|Br|h#Vu0ZIs9w;qzSjx;e0fw@jBr$`_Ip@^dXmiC`soFBaP5X)7Yo+B@A z1&P)4HKn3#dElb==6=viGIeD+ij!-Ic z;$XI%lkv;u_d$5IMzc%b2KnigwZg9oAOJDtvk6@OJY1Z+;XX>91x`AX4qbF|6AF0o zSvi@Jnw1?vo450Q*T-`kEn9XoUQ!wc0TVDK6ZzUKZfM{JZ4(B;yL=idt|XrUByLU9 zIr%^J4|Y5z1p$VoLxaiM&V#t}{vl`J6j}bC{(h5mdAF8$5wSUiC#1JAF=elgPnte` z3Y2&ag3S=d=H{hkW#Z70@8Rs}?H#dWf4o40<+n`UO;;NH?JC2~>Wkokigkt0mWHY| zowZqV<@>-;$vZ^B5kwYQjgH|6W3c*LTfEQ5IvvVkfpd3~8fV2fMyy}alXK1yVLsOT zp03}2i#X`6DdF@2UANEejs}H4q5lBZ-(f7z+5qZYYn;6_DsdXf^U1vtRo5oWiqmw$ znE^D0+55=jczRkkFs~Ys-v$@Zcmmm?&^7d5dQb!WPTzTzIXy-`NGw&b>Oe>6Zqw4xKw8I+DWVov=p!oZJVvsu6c$rfZr+wyEw;vHeO?1lJvUrC&fA{2 zwxL951N^>}R604IG6R6Pu1Z=xC zQv$=i5!fG#c-KJ6u&H~9kab%z7mYG-o%-k3VLQye5(qKz z<5e>_h!-xc>ZYG-S9KA89OLhpw+#uX_#rI-7@!thQBN5wcO^SDSptPe0fDMCxrone z<2PAVEjtED%gcmqY^%;wbWQ})3s(^Zf7C^Vp)kOL=%3wuy);>Mrd|)>t!*S7dj(Qq z;_EAq9g-VrpP>B^jwO}<4)T8FZc{jy5|VU|TuV@GchpI$crTD3oH|IiM99LaU$0=t z=`U7U88*f{zCdB$ovf!j_d-}qjg4=rs@hz>e0lEUrBz$$k2hGq{Vch%c-L*>d8FI1 z$Y!9e+~rM=OLGeOC?2o9h=xoU+9lRCJv}JY5r!7B8rv^28oalfQ35Z)oU z%`2}zg8Tz@_4Vbpx3}f)AAFXwc6sp}c%Jg%!|63|j<9RZ9DV;ndG7b$$WuPDeY|0e zGtpL!=?ojTE+Y4G93$`@2qDG=YU_>^m4~CmUSi)Usv8@#Q#~CD>m6E)sE)|T7#zGs zebA3W zKu0$JVX#ih4I-s7(;Yqcba`?Qq%g%Nu0t`nU2S+HTXC~FEO!%9Y?6F zM6gjOex-T*MG}?)ulG$Jf}QUA;)$+*E?@Egd$aXm=C_9*yDbKxDToCzt3{YIqRi5j z099;iW>Aq@o8Hqfflc=qPM0NZTe-T(jq diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi index e1c70c4f5ade42465e19a07ae24d03d888512a82..3c2d3ff452cd0ce7b379e25ea1f25bcb9b779e80 100644 GIT binary patch delta 21397 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 Date: Sat, 7 Oct 2023 03:52:44 +0200 Subject: [PATCH 19/78] allows janitor keys to be stored in janitor wintercoats and janibets [MDB IGNORE] (#24157) * allows janitor keys to be stored in janitor wintercoats and janibets (#78724) ## About The Pull Request the janitor access key could not be stored on their belt, or their wintercoat slot, which felt off to me. so i added it to both of them, and added the janicart key to the coat slot as well. i figure if one can fit, the other probably can too. ## Why It's Good For The Game convenient, less cumbersome storage for items that you want to keep both close to your person, and accessible for easy use. ## Changelog :cl: qol: allows janitor keys to be stored in janitor wintercoats and janibets /:cl: * allows janitor keys to be stored in janitor wintercoats and janibets --------- Co-authored-by: Lamb <110322848+CoiledLamb@users.noreply.github.com> --- code/game/objects/items/storage/belt.dm | 1 + code/modules/clothing/suits/wintercoats.dm | 2 ++ 2 files changed, 3 insertions(+) diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index dc5f052c13b..abd9f8efb2f 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -694,6 +694,7 @@ atom_storage.max_slots = 6 atom_storage.max_specific_storage = WEIGHT_CLASS_NORMAL // Set to this so the light replacer can fit. atom_storage.set_holdable(list( + /obj/item/access_key, /obj/item/assembly/mousetrap, /obj/item/clothing/gloves, /obj/item/flashlight, diff --git a/code/modules/clothing/suits/wintercoats.dm b/code/modules/clothing/suits/wintercoats.dm index 60cbcae1473..283878339fe 100644 --- a/code/modules/clothing/suits/wintercoats.dm +++ b/code/modules/clothing/suits/wintercoats.dm @@ -247,8 +247,10 @@ icon_state = "coatjanitor" inhand_icon_state = null allowed = list( + /obj/item/access_key, /obj/item/grenade/chem_grenade, /obj/item/holosign_creator, + /obj/item/key/janitor, /obj/item/reagent_containers/cup/beaker, /obj/item/reagent_containers/cup/bottle, /obj/item/reagent_containers/cup/tube, From 542a90fd1353830076853cc3a040546c90a5107c Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sat, 7 Oct 2023 03:53:07 +0200 Subject: [PATCH 20/78] Fix ID card examine runtime [MDB IGNORE] (#24166) * Fix ID card examine runtime (#78785) ## About The Pull Request This always expects to return a list, even if empty. But this early returned before the parent call, meaning it didn't return the empty list (from parent) as expected. ## Changelog :cl: Melbert fix: Fixed an error from reading an ID card closely when you can't read /:cl: * Fix ID card examine runtime --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/game/objects/items/cards_ids.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index eabf8681104..9dd7e125055 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -32,6 +32,7 @@ lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' w_class = WEIGHT_CLASS_TINY + /// Cached icon that has been built for this card. Intended to be displayed in chat. Cardboards IDs and actual IDs use it. var/icon/cached_flat_icon @@ -757,10 +758,10 @@ break /obj/item/card/id/examine_more(mob/user) + . = ..() if(!user.can_read(src)) return - . = ..() . += span_notice("You examine [src] closer, and note the following...") if(registered_age) From e1bcd2c8766780bc552b7ff68b1e8a9b73fb30a0 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sat, 7 Oct 2023 03:53:40 +0200 Subject: [PATCH 21/78] Dogs now flee from mobs with tongs [MDB IGNORE] (#24161) * Dogs now flee from mobs with tongs (#78797) AI dogs with the dog controller behaviour will flee from a target with tongs in hand. Untested because I literally cannot play byond. ## Why It's Good For The Game https://www.youtube.com/watch?v=cXIAZtwvgz0 ## Changelog :cl: oranges add: Dogs now react to centrist grillers more realistically /:cl: * Dogs now flee from mobs with tongs --------- Co-authored-by: oranges --- .../targetting_datums/with_object.dm | 39 +++++++++++++++++++ code/datums/ai/dog/dog_controller.dm | 8 ++++ tgstation.dme | 1 + 3 files changed, 48 insertions(+) create mode 100644 code/datums/ai/basic_mobs/targetting_datums/with_object.dm diff --git a/code/datums/ai/basic_mobs/targetting_datums/with_object.dm b/code/datums/ai/basic_mobs/targetting_datums/with_object.dm new file mode 100644 index 00000000000..039027b6aa5 --- /dev/null +++ b/code/datums/ai/basic_mobs/targetting_datums/with_object.dm @@ -0,0 +1,39 @@ +/** + * Find mobs who are holding the configurable object type + * + * This is an extension of basic targeting behaviour, that allows you to + * only target the mob if they have a specific item in their hand. + * + */ +/datum/targetting_datum/basic/holding_object + // We will find mobs who are holding this object in their hands + var/object_type_path = null + +/** + * Create an instance of the holding object targeting datum + * + * * object_type_path Pass an object type path, this will be compared to the items + * in targets hands to filter the target list. + */ +/datum/targetting_datum/basic/holding_object/New(object_type_path) + if (!ispath(object_type_path)) + stack_trace("trying to create an item targeting datum with no valid typepath") + // Leaving object type as null will make this basically a noop + return + src.object_type_path = object_type_path + +///Returns true or false depending on if the target can be attacked by the mob +/datum/targetting_datum/basic/holding_object/can_attack(mob/living/living_mob, atom/target, vision_range, check_faction = FALSE) + if (object_type_path == null) + return FALSE // no op + if(!ismob(target)) + return FALSE // no hands no problems + + // Look at me, type casting like a grown up + var/mob/targetmob = target + // Check if our parent behaviour agrees we can attack this target (we ignore faction by default) + var/can_attack = ..() + if(can_attack && targetmob.is_holding_item_of_type(object_type_path)) + return TRUE // they have the item + // No valid target + return FALSE diff --git a/code/datums/ai/dog/dog_controller.dm b/code/datums/ai/dog/dog_controller.dm index 5a42cb43a1e..fa0550d0197 100644 --- a/code/datums/ai/dog/dog_controller.dm +++ b/code/datums/ai/dog/dog_controller.dm @@ -18,8 +18,12 @@ /datum/ai_controller/basic_controller/dog/corgi blackboard = list( BB_DOG_HARASS_HARM = TRUE, + // IF you dont have this fleeing behavviour will just refuse to work, isn't that funny ha ha + BB_BASIC_MOB_FLEEING = TRUE, BB_VISION_RANGE = AI_DOG_VISION_RANGE, BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(), + // Find nearby mobs with tongs in hand. + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/holding_object(/obj/item/kitchen/tongs), BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/pet/dog), BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/dog/corgi/puppy = 95, /mob/living/basic/pet/dog/corgi/puppy/void = 5), ) @@ -29,6 +33,10 @@ /datum/ai_planning_subtree/make_babies, // Ian WILL prioritise sex over following your instructions /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/dog_harassment, + // Find targets to run away from (uses the targetting datum from above) + /datum/ai_planning_subtree/simple_find_target, + // Flee from that target + /datum/ai_planning_subtree/flee_target, ) /datum/ai_controller/basic_controller/dog/corgi/get_access() diff --git a/tgstation.dme b/tgstation.dme index 54bbfeb74f2..3352455e4d9 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -938,6 +938,7 @@ #include "code\datums\ai\basic_mobs\pet_commands\play_dead.dm" #include "code\datums\ai\basic_mobs\targetting_datums\basic_targetting_datum.dm" #include "code\datums\ai\basic_mobs\targetting_datums\dont_target_friends.dm" +#include "code\datums\ai\basic_mobs\targetting_datums\with_object.dm" #include "code\datums\ai\cursed\cursed_behaviors.dm" #include "code\datums\ai\cursed\cursed_controller.dm" #include "code\datums\ai\cursed\cursed_subtrees.dm" From 4273388623c91cd6a4606223afe889fba0cc3176 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sat, 7 Oct 2023 03:54:19 +0200 Subject: [PATCH 22/78] offical to official [MDB IGNORE] (#24153) * offical to official (#78762) ## About The Pull Request Fixes "offical" to "official" in several locations - admin fax panel, admin newscaster, art patron text, a photocopier template, and a corgi tail pin item description. Adds this common misspelling to the check_grep.sh ci tool. ## Why It's Good For The Game I have corrected the typo manually every single time I have sent a fax from Central Command. ## Changelog :cl: spellcheck: "offical" has been officially corrected to "official" in several official locations. /:cl: * offical to official --------- Co-authored-by: Isratosh --- code/game/objects/items/tail_pin.dm | 2 +- code/modules/admin/verbs/admin_newscaster.dm | 4 ++-- code/modules/art/paintings.dm | 2 +- config/blanks.json | 2 +- tgui/packages/tgui/interfaces/AdminFax.js | 2 +- tools/ci/check_grep.sh | 5 +++++ 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/code/game/objects/items/tail_pin.dm b/code/game/objects/items/tail_pin.dm index 3052075c94d..de3148dd06d 100644 --- a/code/game/objects/items/tail_pin.dm +++ b/code/game/objects/items/tail_pin.dm @@ -2,7 +2,7 @@ icon = 'icons/obj/poster.dmi' icon_state = "tailpin" name = "tail pin" - desc = "Offically branded 'pin the tail on the corgi' style party implement. Not intended to be used on people." + desc = "Officially branded 'pin the tail on the corgi' style party implement. Not intended to be used on people." force = 0 w_class = WEIGHT_CLASS_SMALL throwforce = 0 diff --git a/code/modules/admin/verbs/admin_newscaster.dm b/code/modules/admin/verbs/admin_newscaster.dm index e6616e2539b..0a25ebaca65 100644 --- a/code/modules/admin/verbs/admin_newscaster.dm +++ b/code/modules/admin/verbs/admin_newscaster.dm @@ -304,7 +304,7 @@ return TRUE var/choice = tgui_alert(usr, "Please confirm feed channel creation","Network Channel Handler", list("Confirm","Cancel")) if(choice == "Confirm") - GLOB.news_network.create_feed_channel(channel_name, "Centcom Offical", channel_desc, locked = channel_locked) + GLOB.news_network.create_feed_channel(channel_name, "Centcom Official", channel_desc, locked = channel_locked) SSblackbox.record_feedback("text", "newscaster_channels", 1, "[channel_name]") creating_channel = FALSE @@ -316,7 +316,7 @@ creating_comment = FALSE return TRUE var/datum/feed_comment/new_feed_comment = new /datum/feed_comment - new_feed_comment.author = "Centcom Offical" + new_feed_comment.author = "Centcom Official" new_feed_comment.body = comment_text new_feed_comment.time_stamp = station_time_timestamp() current_message.comments += new_feed_comment diff --git a/code/modules/art/paintings.dm b/code/modules/art/paintings.dm index e2448c1aacc..9a18a2b0269 100644 --- a/code/modules/art/paintings.dm +++ b/code/modules/art/paintings.dm @@ -249,7 +249,7 @@ painting_metadata.patron_name = user.real_name painting_metadata.credit_value = offer_amount last_patron = WEAKREF(user.mind) - to_chat(user, span_notice("Nanotrasen Trust Foundation thanks you for your contribution. You're now offical patron of this painting.")) + to_chat(user, span_notice("Nanotrasen Trust Foundation thanks you for your contribution. You're now an official patron of this painting.")) var/list/possible_frames = SSpersistent_paintings.get_available_frames(offer_amount) if(possible_frames.len <= 1) // Not much room for choices here. return diff --git a/config/blanks.json b/config/blanks.json index 299fa67a594..f3b38d67bdb 100644 --- a/config/blanks.json +++ b/config/blanks.json @@ -545,7 +545,7 @@ "
", "
By writing and signing this form, you consent to the processing of your personal data by Nanotrasen Corporation.

", "
", - "

Name of offical to take action:

", + "

Name of official to take action:

", "

[___________________________________]

", "

Official Decision:

", "

[___________________________________]

", diff --git a/tgui/packages/tgui/interfaces/AdminFax.js b/tgui/packages/tgui/interfaces/AdminFax.js index e91130baf39..46e12615922 100644 --- a/tgui/packages/tgui/interfaces/AdminFax.js +++ b/tgui/packages/tgui/interfaces/AdminFax.js @@ -91,7 +91,7 @@ export const FaxMainPanel = (props, context) => { icon="n" mr="7px" width="49%" - onClick={() => setPaperName('Nanotrasen Offical Report')}> + onClick={() => setPaperName('Nanotrasen Official Report')}> Nanotrasen