From f2ae3a02467b805a103e14961af918cafc5ca861 Mon Sep 17 00:00:00 2001 From: Momo <136583113+MomoBerri@users.noreply.github.com> Date: Mon, 29 Apr 2024 21:26:56 -0400 Subject: [PATCH 1/4] Labor Camp QOL/Updates (#1781) --- _maps/map_files/Mining/Lavaland.dmm | 220 ++++++++++++++++++++-------- 1 file changed, 159 insertions(+), 61 deletions(-) diff --git a/_maps/map_files/Mining/Lavaland.dmm b/_maps/map_files/Mining/Lavaland.dmm index 5b5b9b45d37b..69cf4ecfe203 100644 --- a/_maps/map_files/Mining/Lavaland.dmm +++ b/_maps/map_files/Mining/Lavaland.dmm @@ -68,6 +68,10 @@ /obj/structure/sign/poster/official/random/directional/west, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) +"an" = ( +/obj/machinery/light/small/directional/west, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) "ao" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, @@ -146,15 +150,6 @@ /turf/open/floor/plating, /area/mine/maintenance/service) "bb" = ( -/obj/structure/closet/crate/internals, -/obj/item/tank/internals/emergency_oxygen, -/obj/item/tank/internals/emergency_oxygen, -/obj/item/tank/internals/emergency_oxygen, -/obj/item/tank/internals/emergency_oxygen, -/obj/item/clothing/mask/breath, -/obj/item/clothing/mask/breath, -/obj/item/clothing/mask/breath, -/obj/item/clothing/mask/breath, /obj/effect/decal/cleanable/dirt, /obj/machinery/power/apc/auto_name/directional/west, /obj/structure/cable, @@ -174,6 +169,10 @@ /obj/effect/turf_decal/siding/yellow, /turf/open/floor/carpet/royalblue, /area/mine/living_quarters) +"bs" = ( +/obj/effect/mob_spawn/corpse/human/charredskeleton, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) "bt" = ( /obj/structure/disposalpipe/segment{ dir = 5 @@ -1815,6 +1814,14 @@ "kY" = ( /turf/closed/wall, /area/mine/cafeteria) +"la" = ( +/obj/effect/decal/cleanable/cobweb/cobweb2, +/obj/effect/decal/cleanable/cobweb, +/obj/effect/decal/cleanable/blood/old, +/obj/item/radio/intercom/prison/directional/south, +/obj/effect/mob_spawn/corpse/human/skeleton, +/turf/open/floor/plating, +/area/mine/maintenance/labor) "lb" = ( /obj/effect/turf_decal/trimline/brown/filled/line{ dir = 6 @@ -2108,6 +2115,10 @@ /obj/structure/cable, /turf/open/floor/iron/dark/textured_large, /area/mine/hydroponics) +"mh" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/plating, +/area/mine/laborcamp) "mj" = ( /obj/structure/stone_tile/block, /obj/structure/stone_tile/block{ @@ -2500,8 +2511,8 @@ "nA" = ( /obj/effect/turf_decal/bot, /obj/structure/ore_box, -/obj/machinery/mineral/labor_points_checker{ - pixel_y = 25 +/obj/structure/sign/poster/official/do_not_question{ + pixel_y = 32 }, /turf/open/floor/iron/smooth_edge, /area/mine/laborcamp/production) @@ -2846,13 +2857,8 @@ /turf/open/floor/iron/dark, /area/mine/hydroponics) "pE" = ( -/obj/effect/spawner/random/trash/hobo_squat, /obj/effect/decal/cleanable/dirt, -/obj/item/seeds/ambrosia, -/obj/item/seeds/tobacco{ - pixel_x = 6; - pixel_y = -4 - }, +/obj/effect/decal/cleanable/cobweb, /turf/open/floor/plating, /area/mine/laborcamp) "pH" = ( @@ -2940,8 +2946,8 @@ "qb" = ( /obj/effect/turf_decal/bot, /obj/structure/ore_box, -/obj/structure/sign/poster/official/do_not_question{ - pixel_y = 32 +/obj/machinery/mineral/labor_points_checker{ + pixel_y = 25 }, /turf/open/floor/iron/smooth, /area/mine/laborcamp/production) @@ -3154,6 +3160,15 @@ "rm" = ( /obj/structure/sign/warning/gas_mask/directional/west, /obj/effect/decal/cleanable/dirt, +/obj/structure/closet/crate/internals, +/obj/item/tank/internals/emergency_oxygen, +/obj/item/tank/internals/emergency_oxygen, +/obj/item/tank/internals/emergency_oxygen, +/obj/item/tank/internals/emergency_oxygen, +/obj/item/clothing/mask/breath, +/obj/item/clothing/mask/breath, +/obj/item/clothing/mask/breath, +/obj/item/clothing/mask/breath, /turf/open/floor/iron/smooth, /area/mine/laborcamp/production) "ro" = ( @@ -3352,6 +3367,13 @@ /obj/structure/sign/departments/medbay/alt/directional/north, /obj/effect/decal/cleanable/dirt, /obj/machinery/light/small/directional/north, +/obj/item/stack/sheet/glass/fifty, +/obj/structure/closet, +/obj/item/weldingtool, +/obj/item/crowbar, +/obj/item/stack/cable_coil/five, +/obj/item/stack/sheet/iron/ten, +/obj/item/stack/rods/ten, /turf/open/floor/iron/smooth_edge, /area/mine/laborcamp/production) "sH" = ( @@ -4306,6 +4328,10 @@ /obj/structure/stone_tile, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) +"yB" = ( +/obj/machinery/light/small/directional/north, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) "yC" = ( /obj/structure/bed{ dir = 4 @@ -4361,6 +4387,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 8 }, +/obj/structure/extinguisher_cabinet/directional/north, /turf/open/floor/iron/smooth, /area/mine/laborcamp/production) "yV" = ( @@ -4563,6 +4590,7 @@ /obj/item/clothing/glasses/meson, /obj/machinery/airalarm/directional/north, /obj/effect/decal/cleanable/dirt, +/obj/item/shovel, /turf/open/floor/iron/smooth, /area/mine/laborcamp/production) "Ac" = ( @@ -5180,7 +5208,8 @@ /area/mine/lounge) "EG" = ( /obj/effect/decal/cleanable/dirt, -/obj/effect/spawner/random/contraband/permabrig_weapon, +/obj/effect/decal/cleanable/cobweb/cobweb2, +/obj/machinery/vending/cigarette, /turf/open/floor/plating, /area/mine/laborcamp) "EH" = ( @@ -5487,6 +5516,17 @@ dir = 4 }, /area/mine/production) +"GO" = ( +/obj/effect/decal/cleanable/dirt, +/obj/effect/spawner/random/contraband/permabrig_weapon, +/obj/item/seeds/ambrosia, +/obj/effect/spawner/random/trash/hobo_squat, +/obj/item/seeds/tobacco{ + pixel_x = 6; + pixel_y = -4 + }, +/turf/open/floor/plating, +/area/mine/laborcamp) "GR" = ( /obj/structure/stone_tile/block/cracked{ dir = 4 @@ -5603,6 +5643,10 @@ dir = 1 }, /area/mine/lounge) +"HC" = ( +/obj/structure/falsewall, +/turf/open/floor/plating, +/area/mine/laborcamp) "HD" = ( /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, @@ -5614,6 +5658,7 @@ /area/mine/laborcamp/security) "HE" = ( /obj/structure/table, +/obj/item/storage/medkit/o2, /turf/open/floor/iron/white, /area/mine/laborcamp/production) "HF" = ( @@ -5720,6 +5765,7 @@ /obj/structure/cable, /obj/machinery/light/small/directional/north, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/air, /turf/open/floor/plating, /area/mine/maintenance/labor) "Ij" = ( @@ -5758,10 +5804,9 @@ /area/mine/laborcamp) "Iy" = ( /obj/structure/table, -/obj/item/storage/medkit/emergency{ - pixel_x = -3 - }, /obj/effect/decal/cleanable/dirt, +/obj/item/storage/medkit/regular, +/obj/item/storage/medkit/emergency, /turf/open/floor/iron/white, /area/mine/laborcamp/production) "IA" = ( @@ -6143,7 +6188,7 @@ /area/mine/laborcamp/production) "Kw" = ( /obj/structure/table, -/obj/item/storage/medkit/regular, +/obj/item/storage/medkit/fire, /turf/open/floor/iron/white, /area/mine/laborcamp/production) "Kz" = ( @@ -6398,7 +6443,11 @@ "LR" = ( /obj/effect/decal/cleanable/dirt, /obj/structure/cable, -/obj/machinery/portable_atmospherics/canister/oxygen, +/obj/structure/closet/crate, +/obj/item/wheelchair, +/obj/item/wheelchair, +/obj/item/clothing/glasses/regular, +/obj/item/clothing/glasses/regular, /turf/open/floor/iron/smooth_edge{ dir = 4 }, @@ -6457,6 +6506,7 @@ /area/mine/lounge) "Mr" = ( /obj/effect/decal/cleanable/dirt, +/obj/structure/ore_box, /turf/open/floor/plating, /area/mine/maintenance/labor) "Mt" = ( @@ -6650,7 +6700,6 @@ /area/mine/eva) "Nq" = ( /obj/effect/turf_decal/bot, -/obj/structure/ore_box, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/decal/cleanable/dirt, /turf/open/floor/iron/smooth_large, @@ -6683,7 +6732,6 @@ /area/mine/cafeteria) "Nz" = ( /obj/effect/turf_decal/bot, -/obj/structure/ore_box, /obj/structure/cable, /obj/effect/decal/cleanable/dirt, /turf/open/floor/iron/smooth_large, @@ -6878,9 +6926,22 @@ /turf/open/floor/iron/dark, /area/mine/laborcamp) "OS" = ( -/obj/structure/closet/secure_closet/brig, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/decal/cleanable/dirt, +/obj/structure/closet, +/obj/item/clothing/under/rank/prisoner, +/obj/item/clothing/under/rank/prisoner, +/obj/item/clothing/under/rank/prisoner, +/obj/item/clothing/under/rank/prisoner, +/obj/item/clothing/under/rank/prisoner/skirt, +/obj/item/clothing/under/rank/prisoner/skirt, +/obj/item/clothing/under/rank/prisoner/skirt, +/obj/item/clothing/under/rank/prisoner/skirt, +/obj/item/clothing/shoes/sneakers/orange, +/obj/item/clothing/shoes/sneakers/orange, +/obj/item/clothing/shoes/sneakers/orange, +/obj/item/clothing/shoes/sneakers/orange, +/obj/item/clothing/shoes/wheelys, /turf/open/floor/iron/smooth_edge, /area/mine/laborcamp/quarters) "OT" = ( @@ -7035,6 +7096,7 @@ "PY" = ( /obj/structure/table, /obj/effect/spawner/random/entertainment/toy, +/obj/item/toy/cards/deck, /turf/open/floor/iron/checker, /area/mine/laborcamp) "Qa" = ( @@ -7057,8 +7119,8 @@ /turf/open/floor/plating, /area/mine/maintenance/service) "Qe" = ( -/obj/structure/ore_box, /obj/effect/decal/cleanable/dirt, +/obj/structure/ore_box, /turf/open/floor/iron/smooth, /area/mine/laborcamp/production) "Qi" = ( @@ -7537,6 +7599,10 @@ /obj/effect/decal/cleanable/dirt, /turf/open/floor/plating, /area/mine/maintenance/living/north) +"Ty" = ( +/obj/machinery/light/small/directional/south, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) "TF" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, @@ -7624,10 +7690,10 @@ /turf/open/misc/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) "Ub" = ( -/obj/item/kirbyplants/random, /obj/machinery/light/small/directional/west, /obj/machinery/airalarm/directional/north, /obj/effect/decal/cleanable/dirt, +/obj/machinery/portable_atmospherics/canister/oxygen, /turf/open/floor/iron/smooth, /area/mine/laborcamp) "Uc" = ( @@ -7716,6 +7782,7 @@ /obj/item/mining_scanner, /obj/item/flashlight, /obj/item/clothing/glasses/meson, +/obj/item/shovel, /turf/open/floor/iron/smooth, /area/mine/laborcamp/production) "Ux" = ( @@ -7956,6 +8023,7 @@ /obj/structure/cable, /obj/effect/spawner/random/trash/grime, /obj/effect/decal/cleanable/dirt, +/obj/structure/extinguisher_cabinet/directional/west, /turf/open/floor/plating, /area/mine/maintenance/labor) "VS" = ( @@ -8096,6 +8164,13 @@ }, /turf/open/floor/plating, /area/mine/maintenance/production) +"WQ" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/effect/decal/cleanable/dirt, +/obj/structure/extinguisher_cabinet/directional/south, +/turf/open/floor/iron/smooth, +/area/mine/laborcamp) "WU" = ( /obj/machinery/space_heater, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, @@ -8272,6 +8347,11 @@ }, /turf/open/floor/iron/dark/textured_large, /area/mine/production) +"XN" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/reagent_dispensers/watertank, +/turf/open/floor/plating, +/area/mine/laborcamp) "XP" = ( /obj/structure/stone_tile/cracked, /obj/structure/stone_tile/block{ @@ -8403,7 +8483,17 @@ "Yr" = ( /obj/structure/table, /obj/effect/decal/cleanable/dirt, -/obj/item/toy/cards/deck, +/obj/item/paper_bin{ + pixel_y = 3 + }, +/obj/item/pen/red{ + pixel_y = 4; + pixel_x = 5 + }, +/obj/item/pen/red{ + pixel_y = 6; + pixel_x = 7 + }, /turf/open/floor/iron/checker, /area/mine/laborcamp) "Ys" = ( @@ -8471,6 +8561,13 @@ }, /turf/open/floor/iron/dark/textured_large, /area/mine/production) +"YV" = ( +/obj/machinery/computer/arcade/battle, +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/cobweb/cobweb2, +/obj/structure/barricade/wooden/crude, +/turf/open/floor/plating, +/area/mine/laborcamp) "YY" = ( /obj/structure/rack, /obj/item/storage/bag/ore, @@ -8479,6 +8576,7 @@ /obj/item/flashlight, /obj/item/clothing/glasses/meson, /obj/effect/decal/cleanable/dirt, +/obj/item/shovel, /turf/open/floor/iron/smooth, /area/mine/laborcamp/production) "Zb" = ( @@ -23256,7 +23354,7 @@ pU pU pU pU -pU +Ty ff ff eR @@ -23266,7 +23364,7 @@ gK ff eR ff -pU +yB pU uU uU @@ -24301,7 +24399,7 @@ iw Te pU pU -pU +Pc pU uU uU @@ -24557,7 +24655,7 @@ Ge Ge Ge eb -pU +yB pU pU uU @@ -24815,7 +24913,7 @@ CB eT Te pU -pU +Pc pU uU uU @@ -25054,9 +25152,9 @@ pU pU aj aj -aj -uU -uU +pU +Zt +YV ff nA oq @@ -25311,9 +25409,9 @@ yY FZ fO aj -aj pU -uU +Zt +Zt ff qb BA @@ -25569,7 +25667,7 @@ RP qE aj aj -aj +pU Zt Zt om @@ -26613,7 +26711,7 @@ Ge Ge Ge Ge -pU +yB mK OH pU @@ -26866,11 +26964,11 @@ jA Ix OK tr -Zt +HC pE +GO +Zt Zt -pU -pU uU uU pU @@ -27125,9 +27223,9 @@ pH zr Zt EG -rX -pU -uU +mh +XN +Zt uU uU uU @@ -27376,7 +27474,7 @@ pc RZ tZ EQ -Tu +WQ Zt Zt jm @@ -27626,7 +27724,7 @@ qE pU aj aj -aj +pU Zt Vm mP @@ -27883,7 +27981,7 @@ Kz pU aj aj -aj +pU Zt nI sU @@ -28140,7 +28238,7 @@ pU aj aj aj -aj +pU rX wu VS @@ -28414,7 +28512,7 @@ Ee ve RD RD -uU +RD uU uU uU @@ -28670,8 +28768,8 @@ rF rF Hb RD -uU -uU +la +RD uU uU uU @@ -28927,8 +29025,8 @@ CD Vq Ha RD -uU -uU +RD +RD uU uU uU @@ -29436,7 +29534,7 @@ Od qx Od pU -pU +an dJ rF JE @@ -29942,7 +30040,7 @@ aj aj aj aj -pU +bs aj aj aj @@ -33563,7 +33661,7 @@ pU pU pU aj -pU +bs aj aj aj From e3f0e2b71a6b07c1dc788702d186afb50866990f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 01:27:15 +0000 Subject: [PATCH 2/4] Automatic changelog for PR #1781 [ci skip] --- html/changelogs/AutoChangeLog-pr-1781.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1781.yml diff --git a/html/changelogs/AutoChangeLog-pr-1781.yml b/html/changelogs/AutoChangeLog-pr-1781.yml new file mode 100644 index 000000000000..1a0838f95150 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1781.yml @@ -0,0 +1,4 @@ +author: "MomoBerri" +delete-after: True +changes: + - qol: "several updates to the labor camp" \ No newline at end of file From 84f033421e499c615e3b2eb22cc433a41ed5e298 Mon Sep 17 00:00:00 2001 From: Gboster-0 <82319946+Gboster-0@users.noreply.github.com> Date: Tue, 30 Apr 2024 03:29:51 +0200 Subject: [PATCH 3/4] Adds Cortical borers, in their full worm glory (#976) * broken initial commit * actually working worms * removes the mid-round chance * readme.md changes part 1 * readme.md changes part 2 * mayhaps we dont support jsx files? * traitor borers & code improvement * im sorry, but we cant have eggs running around can we? * lets not * post-upstream fixes * entering hosts now gives a text * lets learn chemicals by names, and not a path * fixes * requested changes * better blood learning * alphabetical lists * moar variables for controlling ability requirements * removes the debug borer (it isnt helpfull for debugging, may return later) * midround borers maybe? * le midround changes * adds some spaces, fixes a minor edge-case bug * borer objectives * borer hiding ability cleanup * some cleanup * small objective text change * requested changes * some misc things * boop * whoops, forgot dat * lil bit of improvements * better objectives * whoops * bap * taking away the neutered borers balls that i already took * simplifies the cage * sets it on a major track, didnt know how to do dat before * TGUI with help, and borer egg fixes * adds borers to the antag token thingy * some re-naming, neutered round-end section, eyecandy for the borer cage * "i'll just quickly add in one line of logging" i said, "it surelly wont take 4 commits" i said... * that isnt a proper comment * Update readme.md * fixes the borer cage's animation * bit more TGUI work * misc fixes * the grand borer warfare DLC * switch around the chemicals we know about * quite a few fixes * cortical names, mainly for admin reports * name fixes * adds the OH_GOD_WE_FUCKED_UP() proc * fixes * initial suggested changes * misc fixes, an evolution var for added actions * no more infinitelly trapped borers in dead people * Create screenshot_antag_icons_borer.png * le updates to polling * opendream lints got mad * screenshot tests * in this part, absolucy fixes everything by changing 1 line of code * some minor stuff * yoinks https://github.com/Skyrat-SS13/Skyrat-tg/pull/26693 * fixes * long * co-existance * god i hate AI's breaking everything * doubles point gain --------- Co-authored-by: dwasint <82520990+dwasint@users.noreply.github.com> --- code/__DEFINES/mobs.dm | 5 +- code/__DEFINES/research/anomalies.dm | 1 + code/__DEFINES/role_preferences.dm | 2 + code/__DEFINES/span.dm | 1 + .../~monkestation/actionspeed_modification.dm | 1 + code/__DEFINES/~monkestation/antagonists.dm | 25 + code/_globalvars/lists/poll_ignore.dm | 2 + code/datums/components/squashable.dm | 2 + code/datums/mutations/_mutations.dm | 5 + code/modules/admin/sql_ban_system.dm | 1 + .../antagonists/changeling/powers/panacea.dm | 6 + code/modules/assembly/mousetrap.dm | 7 + .../client/preferences/middleware/antags.dm | 1 + .../screenshot_antag_icons_corticalborer.png | Bin 0 -> 357 bytes .../borers/code/abilities/_ability.dm | 79 +++ .../code/abilities/chemical_injector.dm | 101 ++++ .../borers/code/abilities/enter_host.dm | 142 +++++ .../borers/code/abilities/evolution_tree.dm | 94 +++ .../borers/code/abilities/force_speech.dm | 31 + .../borers/code/abilities/hide_presence.dm | 31 + .../borers/code/abilities/host_healthscan.dm | 19 + .../borers/code/abilities/incite_fear.dm | 63 +++ .../borers/code/abilities/learn_chemicals.dm | 145 +++++ .../borers/code/abilities/learn_focus.dm | 36 ++ .../borers/code/abilities/revive_host.dm | 45 ++ .../borers/code/abilities/spawn_offspring.dm | 117 ++++ .../borers/code/abilities/toggle_stealth.dm | 26 + .../borers/code/abilities/upgrade_body.dm | 32 ++ .../borers/code/abilities/willing_host.dm | 36 ++ .../code/antagonist_stuff/antagonist_datum.dm | 128 +++++ .../code/antagonist_stuff/midround_event.dm | 124 ++++ .../code/antagonist_stuff/round_end_text.dm | 58 ++ .../borers/code/cortical_borer_chems.dm | 21 + .../borers/code/evolution/borer_evolution.dm | 17 + .../borers/code/evolution/evolution_datum.dm | 52 ++ .../code/evolution/evolution_diveworm.dm | 107 ++++ .../code/evolution/evolution_general.dm | 78 +++ .../code/evolution/evolution_hivelord.dm | 63 +++ .../code/evolution/evolution_symbiote.dm | 103 ++++ .../antagonists/borers/code/focus_datum.dm | 78 +++ .../borers/code/items/borer_spawner.dm | 95 ++++ .../antagonists/borers/code/items/egg.dm | 83 +++ .../borers/code/items/empowered_egg.dm | 66 +++ .../borers/code/items/imprisonment_cage.dm | 103 ++++ .../borers/code/mobs/cortical_borer.dm | 534 ++++++++++++++++++ .../borers/code/mobs/empowered_borer.dm | 17 + .../borers/code/mobs/name_lists.dm | 56 ++ .../borers/code/mobs/neutered_borer.dm | 18 + .../antagonists/borers/code/status_effects.dm | 11 + .../antagonists/borers/icons/actions.dmi | Bin 0 -> 6184 bytes .../antagonists/borers/icons/animal.dmi | Bin 0 -> 2000 bytes .../modules/antagonists/borers/icons/hud.dmi | Bin 0 -> 277 bytes .../antagonists/borers/icons/items.dmi | Bin 0 -> 1007 bytes .../code/modules/antagonists/borers/readme.md | 41 ++ .../code/modules/cargo/crates/security.dm | 8 + .../job_helpers/organ_printer.dm | 2 +- .../code/modules/uplink/uplink_items/misc.dm | 10 + tgstation.dme | 36 ++ .../tgui/interfaces/AntagInfoBorer.tsx | 403 +++++++++++++ tgui/packages/tgui/interfaces/BorerChem.js | 60 ++ .../tgui/interfaces/BorerEvolution.tsx | 142 +++++ .../antagonists/antagonists/corticalborer.ts | 17 + 62 files changed, 3585 insertions(+), 2 deletions(-) create mode 100644 code/__DEFINES/~monkestation/actionspeed_modification.dm create mode 100644 code/modules/unit_tests/screenshots/screenshot_antag_icons_corticalborer.png create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/_ability.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/chemical_injector.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/enter_host.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/evolution_tree.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/force_speech.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/hide_presence.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/host_healthscan.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/incite_fear.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/learn_chemicals.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/learn_focus.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/revive_host.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/spawn_offspring.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/toggle_stealth.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/upgrade_body.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/abilities/willing_host.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/antagonist_stuff/antagonist_datum.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/antagonist_stuff/midround_event.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/antagonist_stuff/round_end_text.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/cortical_borer_chems.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/evolution/borer_evolution.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/evolution/evolution_datum.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/evolution/evolution_diveworm.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/evolution/evolution_general.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/evolution/evolution_hivelord.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/evolution/evolution_symbiote.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/focus_datum.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/items/borer_spawner.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/items/egg.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/items/empowered_egg.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/items/imprisonment_cage.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/mobs/cortical_borer.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/mobs/empowered_borer.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/mobs/name_lists.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/mobs/neutered_borer.dm create mode 100644 monkestation/code/modules/antagonists/borers/code/status_effects.dm create mode 100644 monkestation/code/modules/antagonists/borers/icons/actions.dmi create mode 100644 monkestation/code/modules/antagonists/borers/icons/animal.dmi create mode 100644 monkestation/code/modules/antagonists/borers/icons/hud.dmi create mode 100644 monkestation/code/modules/antagonists/borers/icons/items.dmi create mode 100644 monkestation/code/modules/antagonists/borers/readme.md create mode 100644 tgui/packages/tgui/interfaces/AntagInfoBorer.tsx create mode 100644 tgui/packages/tgui/interfaces/BorerChem.js create mode 100644 tgui/packages/tgui/interfaces/BorerEvolution.tsx create mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/corticalborer.ts diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index b1a96d7bb881..2618161affc6 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -577,8 +577,11 @@ ///Whether or not the squashing requires the squashed mob to be lying down #define SQUASHED_SHOULD_BE_DOWN (1<<0) ///Whether or not to gib when the squashed mob is moved over -#define SQUASHED_SHOULD_BE_GIBBED (1<<0) +#define SQUASHED_SHOULD_BE_GIBBED (1<<1) + +/// Don't squash our mob if its not located in a turf +#define SQUASHED_DONT_SQUASH_IN_CONTENTS (1<<3) /* * Defines for "AI emotions", allowing the AI to expression emotions * with status displays via emotes. diff --git a/code/__DEFINES/research/anomalies.dm b/code/__DEFINES/research/anomalies.dm index 12a114439c7d..3a6b02f91888 100644 --- a/code/__DEFINES/research/anomalies.dm +++ b/code/__DEFINES/research/anomalies.dm @@ -44,6 +44,7 @@ GLOBAL_LIST_INIT(bioscrambler_organs_blacklist, typecacheof(list ( /obj/item/organ/internal/monster_core, /obj/item/organ/internal/vocal_cords/colossus, /obj/item/organ/internal/zombie_infection, + /obj/item/organ/internal/empowered_borer_egg, // MONKESTATION ADDITION -- CORTICAL_BORERS ))) /// List of body parts we can apply to people diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 20e253c75d27..d84038f139f3 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -40,6 +40,7 @@ #define ROLE_SPIDER "Spider" #define ROLE_WIZARD_MIDROUND "Wizard (Midround)" // monke midrounds +#define ROLE_CORTICAL_BORER "Cortical Borer" #define ROLE_DRIFTING_CONTRACTOR "Drifting Contractor" #define ROLE_FLORIDA_MAN "Florida Man" #define ROLE_SLASHER "Slasher" @@ -152,6 +153,7 @@ GLOBAL_LIST_INIT(special_roles, list( ROLE_SPIDER = 0, ROLE_WIZARD_MIDROUND = 14, //monkestation edit start + ROLE_CORTICAL_BORER = 0, // Module ID: CORTICAL_BORERS ROLE_DRIFTING_CONTRACTOR = 0, ROLE_VAMPIRICACCIDENT = 0, ROLE_MONSTERHUNTER = 0, diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm index 7efc2230cfa0..07a1f9d93f42 100644 --- a/code/__DEFINES/span.dm +++ b/code/__DEFINES/span.dm @@ -54,6 +54,7 @@ #define span_greenteamradio(str) ("" + str + "") #define span_greentext(str) ("" + str + "") #define span_grey(str) ("" + str + "") +#define span_header(str) ("" + str + "") #define span_hear(str) ("" + str + "") #define span_hidden(str) ("") #define span_hierophant(str) ("" + str + "") diff --git a/code/__DEFINES/~monkestation/actionspeed_modification.dm b/code/__DEFINES/~monkestation/actionspeed_modification.dm new file mode 100644 index 000000000000..1d7c683c7e6d --- /dev/null +++ b/code/__DEFINES/~monkestation/actionspeed_modification.dm @@ -0,0 +1 @@ +#define ACTIONSPEED_ID_BORER "borer" diff --git a/code/__DEFINES/~monkestation/antagonists.dm b/code/__DEFINES/~monkestation/antagonists.dm index 8ec0def3b22b..b42eb8c6d574 100644 --- a/code/__DEFINES/~monkestation/antagonists.dm +++ b/code/__DEFINES/~monkestation/antagonists.dm @@ -17,3 +17,28 @@ #define iscogscarab(checked) (istype(checked, /mob/living/basic/drone/cogscarab)) /// is something an eminence #define iseminence(checked) (istype(checked, /mob/living/eminence)) + +/// is something a worm +#define iscorticalborer(A) (istype(A, /mob/living/basic/cortical_borer)) + +// Borer evolution defines +// The three primary paths that eventually diverge +#define BORER_EVOLUTION_SYMBIOTE "Symbiote" +#define BORER_EVOLUTION_HIVELORD "Hivelord" +#define BORER_EVOLUTION_DIVEWORM "Diveworm" +// Just general upgrades that don't take you in a specific direction +#define BORER_EVOLUTION_GENERAL "General" +#define BORER_EVOLUTION_START "Start" + +// Borer effect flags + +/// If the borer is in stealth mode, giving less feedback to hosts at the cost of no health/resource/point gain +#define BORER_STEALTH_MODE (1<<0) +/// If the borer is sugar-immune, taking no ill effects from sugar +#define BORER_SUGAR_IMMUNE (1<<1) +/// If the borer is able to enter hosts in half the time, if not hiding +#define BORER_FAST_BORING (1<<2) +/// If the borer is currently hiding under tables/couches/stairs or appearing on top of them +#define BORER_HIDING (1<<3) +/// If the borer can produce eggs without a host +#define BORER_ALONE_PRODUCTION (1<<4) diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm index c3fbb720650f..eee6e1b92910 100644 --- a/code/_globalvars/lists/poll_ignore.dm +++ b/code/_globalvars/lists/poll_ignore.dm @@ -9,6 +9,7 @@ #define POLL_IGNORE_CARGORILLA "cargorilla" #define POLL_IGNORE_CONTRACTOR_SUPPORT "contractor_support" #define POLL_IGNORE_CONSTRUCT "construct" +#define POLL_IGNORE_CORTICAL_BORER "cortical_borer" // MONKESTATION ADDITION -- CORTICAL_BORERS #define POLL_IGNORE_DRONE "drone" #define POLL_IGNORE_FIRE_SHARK "fire_shark" #define POLL_IGNORE_FUGITIVE "fugitive" @@ -47,6 +48,7 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list( POLL_IGNORE_CARGORILLA = "Cargorilla", POLL_IGNORE_CONTRACTOR_SUPPORT = "Contractor Support Unit", POLL_IGNORE_CONSTRUCT = "Construct", + POLL_IGNORE_CORTICAL_BORER = "Cortical Borer", // MONKESTATION ADDITION -- CORTICAL_BORERS POLL_IGNORE_DRONE = "Drone shells", POLL_IGNORE_FIRE_SHARK = "Fire Shark", POLL_IGNORE_FUGITIVE = "Fugitive Hunter", diff --git a/code/datums/components/squashable.dm b/code/datums/components/squashable.dm index 0e091fa1b48a..5e74047914cc 100644 --- a/code/datums/components/squashable.dm +++ b/code/datums/components/squashable.dm @@ -37,6 +37,8 @@ return var/mob/living/parent_as_living = parent + if((squash_flags & SQUASHED_DONT_SQUASH_IN_CONTENTS) && !isturf(parent_as_living.loc)) + return if(squash_flags & SQUASHED_SHOULD_BE_DOWN && parent_as_living.body_position != LYING_DOWN) return diff --git a/code/datums/mutations/_mutations.dm b/code/datums/mutations/_mutations.dm index be1f45defa40..c11f63b101e5 100644 --- a/code/datums/mutations/_mutations.dm +++ b/code/datums/mutations/_mutations.dm @@ -90,6 +90,11 @@ /datum/mutation/human/proc/on_acquiring(mob/living/carbon/human/acquirer) if(!acquirer || !istype(acquirer) || acquirer.stat == DEAD || (src in acquirer.dna.mutations)) return TRUE + // MONKESTATION ADDITION START -- CORTICAL_BORERS + if(acquirer.has_borer()) + to_chat(acquirer, span_warning("Something inside holds dearly to your humanity!")) + return TRUE + // MONKESTATION ADDITION END if(species_allowed && !species_allowed.Find(acquirer.dna.species.id)) return TRUE if(health_req && acquirer.health < health_req) diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm index 06943b4a5c57..9dc31be8498f 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -348,6 +348,7 @@ ROLE_BROTHER, ROLE_BLOODSUCKER, ROLE_BLOODSUCKERBREAKOUT, + ROLE_CORTICAL_BORER, // MONKESTATION ADDITION -- CORTICAL_BORERS ROLE_CHANGELING, ROLE_CLOCK_CULTIST, ROLE_CULTIST, diff --git a/code/modules/antagonists/changeling/powers/panacea.dm b/code/modules/antagonists/changeling/powers/panacea.dm index dff8cd7f7365..fec074885459 100644 --- a/code/modules/antagonists/changeling/powers/panacea.dm +++ b/code/modules/antagonists/changeling/powers/panacea.dm @@ -15,6 +15,7 @@ user.get_organ_by_type(/obj/item/organ/internal/body_egg), user.get_organ_by_type(/obj/item/organ/internal/legion_tumour), user.get_organ_by_type(/obj/item/organ/internal/zombie_infection), + user.get_organ_by_type(/obj/item/organ/internal/empowered_borer_egg), // MONKESTATION ADDITION -- CORTICAL_BORERS ) for(var/o in bad_organs) @@ -28,6 +29,11 @@ C.vomit(0) O.forceMove(get_turf(user)) + // MONKESTATION ADDITION START -- CORTICAL_BORERS + var/mob/living/basic/cortical_borer/brain_pest = user.has_borer() + if(brain_pest) + brain_pest.leave_host() + // MONKESTATION ADDITION END user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10) user.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 20) user.reagents.add_reagent(/datum/reagent/medicine/antihol, 10) diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index b71388e13cbf..b20f2628a82a 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -147,6 +147,13 @@ else if(isregalrat(target)) visible_message(span_boldannounce("Skreeeee!")) //He's simply too large to be affected by a tiny mouse trap. + // MONKESTATION ADDITION START -- ID:CORTICAL_BORERS + else if(iscorticalborer(target)) + var/mob/living/basic/cortical_borer/pest = target + visible_message(span_boldannounce("SPLAT!")) + pest.adjust_health(50) + // MONKESTATION ADDITION END + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) pulse() diff --git a/code/modules/client/preferences/middleware/antags.dm b/code/modules/client/preferences/middleware/antags.dm index 4e5d66e3b97d..5239fa9a9953 100644 --- a/code/modules/client/preferences/middleware/antags.dm +++ b/code/modules/client/preferences/middleware/antags.dm @@ -120,6 +120,7 @@ ROLE_LONE_OPERATIVE = /datum/antagonist/nukeop/lone, ROLE_SENTIENCE = /datum/antagonist/sentient_creature, //monkestation antags + ROLE_CORTICAL_BORER = /datum/antagonist/cortical_borer, ROLE_DRIFTING_CONTRACTOR = /datum/antagonist/traitor/contractor, ROLE_SLASHER = /datum/antagonist/slasher, ROLE_FLORIDA_MAN = /datum/antagonist/florida_man diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_corticalborer.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_corticalborer.png new file mode 100644 index 0000000000000000000000000000000000000000..35899cffa63171ae80c02eaf6221a3c26c8f4010 GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoSydqsB`&GO$wiq3C7Jno3=9=> zg2M`mO22;zF8KKMiI%sn*10q1gExd4Tr__0Nawtd=E;B!p&LvdKRV-a*5|xNV?md< zkav)1j_8yrJ1l~Yy1Rf(o}Ct#yM&5^6?0Nq!|lac#pC_;80MXnkoPHJ@BrGn&C|s( zq+-t7tA>1zjsgq;Q@B(ESC{U;WZf{ofUC1wFkVqBYx{&3E~^7pt6UaP+9-W*b(vAL zkI~GTQM^Fy_&|evh=1$>>tmDeef#&WWcKtK*7Mh`mC%3I{&4%NUoOg}jQ{_jMKS;k3NZEIeTbo65AiTwU4AHEuoKynp?zHT}CKo^P4t a cortical_owner.chemical_storage) ? TRUE : FALSE + + var/chemicals[0] + for(var/reagent in cortical_owner.known_chemicals) + var/datum/reagent/temp = GLOB.chemical_reagents_list[reagent] + if(temp) + var/chemname = temp.name + chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name)))) + data["chemicals"] = chemicals + + return data + +/datum/action/cooldown/borer/inject_chemical/ui_act(action, params) + . = ..() + if(.) + return + var/mob/living/basic/cortical_borer/cortical_owner = owner + switch(action) + if("amount") + var/target = text2num(params["target"]) + if(target in cortical_owner.injection_rates) + cortical_owner.injection_rate_current = target + . = TRUE + if("inject") + if(!iscorticalborer(usr) || !COOLDOWN_FINISHED(cortical_owner, injection_cooldown)) + return + if(cortical_owner.host_sugar()) + owner.balloon_alert(owner, "cannot function with sugar in host") + return + var/reagent_name = params["reagent"] + var/reagent = GLOB.name2reagent[reagent_name] + if(!(reagent in cortical_owner.known_chemicals)) + return + + cortical_owner.reagent_holder.reagents.add_reagent(reagent, cortical_owner.injection_rate_current, added_purity = 1) + cortical_owner.reagent_holder.reagents.trans_to(cortical_owner.human_host, cortical_owner.injection_rate_current, methods = INGEST) + + to_chat(cortical_owner.human_host, span_warning("You feel something cool inside of you and a dull ache in your head!")) + cortical_owner.chemical_storage -= cortical_owner.injection_rate_current * CHEMICALS_PER_UNIT + COOLDOWN_START(cortical_owner, injection_cooldown, (cortical_owner.injection_rate_current / CHEMICAL_SECOND_DIVISOR)) + + var/turf/human_turf = get_turf(cortical_owner.human_host) + var/logging_text = "[key_name(cortical_owner)] injected [key_name(cortical_owner.human_host)] with [reagent_name] at [loc_name(human_turf)]" + cortical_owner.log_message(logging_text, LOG_GAME) + cortical_owner.human_host.log_message(logging_text, LOG_GAME) + . = TRUE + +/datum/action/cooldown/borer/inject_chemical/ui_state(mob/user) + return GLOB.always_state + +/datum/action/cooldown/borer/inject_chemical/ui_status(mob/user, datum/ui_state/state) + if(!iscorticalborer(user)) + return UI_CLOSE + + var/mob/living/basic/cortical_borer/borer = user + + if(!borer.human_host) + return UI_CLOSE + return ..() + +#undef CHEMICALS_PER_UNIT +#undef CHEMICAL_SECOND_DIVISOR diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/enter_host.dm b/monkestation/code/modules/antagonists/borers/code/abilities/enter_host.dm new file mode 100644 index 000000000000..2b96a3be3da8 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/enter_host.dm @@ -0,0 +1,142 @@ +//to either get inside, or out, of a host +/datum/action/cooldown/borer/choosing_host + name = "Inhabit/Uninhabit Host" + cooldown_time = 10 SECONDS + button_icon_state = "host" + ability_explanation = "\ + Using this ability we can eighter enter or exit a host.\n\ + Whilst leaving a host, they cannot have sugar within them and we require to be carefull in order to not immediatelly get squished.\n\ + Going inside of a host will usually take 6 seconds if we are not a hivelord, we must take causion for the host to not move.\n\ + Whilst going inside of a host we require the following:\n\ + - they must not have one of us within them\n\ + - they must be of compatible species\n\ + - and they must not have helmets designed against us\n\ + " + +/datum/action/cooldown/borer/choosing_host/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + + //having a host means we need to leave them + if(cortical_owner.human_host) + if(cortical_owner.host_sugar()) + if(cortical_owner.human_host.stat != DEAD) + owner.balloon_alert(owner, "cannot function with sugar in host") + return + // we have a host with sugar and our host is dead. Amazing fuckup + owner.balloon_alert(owner, "Struggling to leave") + to_chat(cortical_owner, span_userdanger("We struggle to leave our host, barelly able to due to the sugar in their blood no longer moving, this will take time...")) + StartCooldown(30 SECONDS) // stay in place now + sleep(30 SECONDS) + + owner.balloon_alert(owner, "detached from host") + if(!(cortical_owner.upgrade_flags & BORER_STEALTH_MODE)) + to_chat(cortical_owner.human_host, span_notice("Something carefully tickles your inner ear...")) + + //log the interaction + var/turf/human_turfone = get_turf(cortical_owner.human_host) + var/logging_text = "[key_name(cortical_owner)] left [key_name(cortical_owner.human_host)] at [loc_name(human_turfone)]" + cortical_owner.log_message(logging_text, LOG_GAME) + cortical_owner.human_host.log_message(logging_text, LOG_GAME) + + var/obj/item/organ/internal/borer_body/borer_organ = locate() in cortical_owner.human_host.organs + if(borer_organ) + borer_organ.Remove(cortical_owner.human_host) + + cortical_owner.forceMove(human_turfone) + cortical_owner.human_host = null + REMOVE_TRAIT(cortical_owner, TRAIT_WEATHER_IMMUNE, "borer_in_host") + + StartCooldown() + return + + //we dont have a host so lets inhabit one + var/list/usable_hosts = list() + for(var/mob/living/carbon/human/listed_human in range(1, cortical_owner)) + // no non-human hosts + if(!ishuman(listed_human) || ismonkey(listed_human)) + to_chat(cortical_owner, span_warning("[listed_human] is not a human!")) + continue + // cannot have multiple borers (for now) + if(listed_human.has_borer()) + to_chat(cortical_owner, span_warning("[listed_human] already has our sister within them!")) + continue + // hosts need to be organic + if(!(listed_human.dna.species.inherent_biotypes & MOB_ORGANIC) && cortical_owner.organic_restricted) + to_chat(cortical_owner, span_warning("[listed_human] has incompatible biology with us!")) + continue + // hosts NEED to be organic + if(!(listed_human.mob_biotypes & MOB_ORGANIC) && cortical_owner.organic_restricted) + to_chat(cortical_owner, span_warning("[listed_human] has incompatible biology with us!")) + continue + // hosts cannot be changelings unless we specify otherwise + if(listed_human.mind) + var/datum/antagonist/changeling/changeling = listed_human.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling && cortical_owner.changeling_restricted) + to_chat(cortical_owner, span_warning("[listed_human] has incompatible biology with us!")) + continue + // hosts cannot have bio protected headgear + if(check_for_bio_protection(listed_human) == TRUE) + to_chat(cortical_owner, span_warning("[listed_human] has too hard of a helmet to crawl inside of their ear!")) + continue + usable_hosts += listed_human + + //if the list of possible hosts is one, just go straight in, no choosing + if(length(usable_hosts) == 1) + enter_host(usable_hosts[1]) + return + + //if the list of possible host is more than one, allow choosing a host + var/choose_host = tgui_input_list(cortical_owner, "Choose your host!", "Host Choice", usable_hosts) + if(!choose_host) + owner.balloon_alert(owner, "no target selected") + return + enter_host(choose_host) + +/datum/action/cooldown/borer/choosing_host/proc/enter_host(mob/living/carbon/human/singular_host) + var/mob/living/basic/cortical_borer/cortical_owner = owner + if(check_for_bio_protection(singular_host)) + owner.balloon_alert(owner, "target head too protected!") + return + if(singular_host.has_borer()) + owner.balloon_alert(owner, "target already occupied") + return + if(!do_after(cortical_owner, (((cortical_owner.upgrade_flags & BORER_FAST_BORING) && !(cortical_owner.upgrade_flags & BORER_HIDING)) ? 3 SECONDS : 6 SECONDS), target = singular_host)) + owner.balloon_alert(owner, "you and target must be still") + return + if(get_dist(singular_host, cortical_owner) > 1) + owner.balloon_alert(owner, "target too far away") + return + cortical_owner.human_host = singular_host + cortical_owner.forceMove(cortical_owner.human_host) + if(!(cortical_owner.upgrade_flags & BORER_STEALTH_MODE)) + to_chat(cortical_owner.human_host, span_notice("A chilling sensation goes down your spine...")) + + cortical_owner.copy_languages(cortical_owner.human_host) + + var/obj/item/organ/internal/borer_body/borer_organ = new(cortical_owner.human_host) + borer_organ.borer = owner + borer_organ.Insert(cortical_owner.human_host) + + var/turf/human_turftwo = get_turf(cortical_owner.human_host) + var/logging_text = "[key_name(cortical_owner)] went into [key_name(cortical_owner.human_host)] at [loc_name(human_turftwo)]" + cortical_owner.log_message(logging_text, LOG_GAME) + cortical_owner.human_host.log_message(logging_text, LOG_GAME) + + ADD_TRAIT(cortical_owner, TRAIT_WEATHER_IMMUNE, "borer_in_host") + StartCooldown() + +/// Checks if the target's head is bio protected, returns true if this is the case +/datum/action/cooldown/borer/choosing_host/proc/check_for_bio_protection(mob/living/carbon/human/target) + if(isobj(target.head)) + if(target.head.get_armor_rating(BIO) >= 100) + return TRUE + if(isobj(target.wear_mask)) + if(target.wear_mask.get_armor_rating(BIO) >= 100) + return TRUE + if(isobj(target.wear_neck)) + if(target.wear_neck.get_armor_rating(BIO) >= 100) + return TRUE + return FALSE diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/evolution_tree.dm b/monkestation/code/modules/antagonists/borers/code/abilities/evolution_tree.dm new file mode 100644 index 000000000000..8783099f8b12 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/evolution_tree.dm @@ -0,0 +1,94 @@ +/datum/action/cooldown/borer/evolution_tree + name = "Open Evolution Tree" + button_icon_state = "newability" + ability_explanation = "\ + Allows you to evolve essential to survive abilities.\n\ + Beware, as evolving a tier 3 path will lock you out of all other tier 3 paths.\n\ + - The Diveworm path focuses on killing hosts, and making eggs in their corpses.\n\ + - The Hivelord path focuses on making lots of eggs.\n\ + - The Symbiote path focuses on helping their host, for mutual benefit.\n\ + " + +/datum/action/cooldown/borer/evolution_tree/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + ui_interact(owner) + +/datum/action/cooldown/borer/evolution_tree/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "BorerEvolution", name) + ui.open() + +/datum/action/cooldown/borer/evolution_tree/ui_data(mob/user) + var/list/data = list() + + var/static/list/path_to_color = list( + BORER_EVOLUTION_DIVEWORM = "red", + BORER_EVOLUTION_HIVELORD = "purple", + BORER_EVOLUTION_SYMBIOTE = "green", + BORER_EVOLUTION_GENERAL = "label", + ) + + var/mob/living/basic/cortical_borer/cortical_owner = owner + + data["evolution_points"] = cortical_owner.stat_evolution + + for(var/datum/borer_evolution/evolution as anything in cortical_owner.get_possible_evolutions()) + if(evolution in cortical_owner.past_evolutions) + continue + + var/list/evo_data = list() + + evo_data["path"] = evolution + evo_data["name"] = initial(evolution.name) + evo_data["desc"] = initial(evolution.desc) + evo_data["gainFlavor"] = initial(evolution.gain_text) + evo_data["cost"] = initial(evolution.evo_cost) + evo_data["disabled"] = ((initial(evolution.evo_cost) > cortical_owner.stat_evolution) || (initial(evolution.mutually_exclusive) && cortical_owner.genome_locked)) + evo_data["evoPath"] = initial(evolution.evo_type) + evo_data["color"] = path_to_color[initial(evolution.evo_type)] || "grey" + evo_data["tier"] = initial(evolution.tier) + evo_data["exclusive"] = initial(evolution.mutually_exclusive) + + data["learnableEvolution"] += list(evo_data) + + for(var/path in cortical_owner.past_evolutions) + var/list/evo_data = list() + var/datum/borer_evolution/found_evolution = cortical_owner.past_evolutions[path] + + evo_data["name"] = found_evolution.name + evo_data["desc"] = found_evolution.desc + evo_data["gainFlavor"] = found_evolution.gain_text + evo_data["cost"] = found_evolution.evo_cost + evo_data["evoPath"] = found_evolution.evo_type + evo_data["color"] = path_to_color[found_evolution.evo_type] || "grey" + evo_data["tier"] = found_evolution.tier + + data["learnedEvolution"] += list(evo_data) + return data + +/datum/action/cooldown/borer/evolution_tree/ui_act(action, params) + . = ..() + if(.) + return + var/mob/living/basic/cortical_borer/cortical_owner = owner + switch(action) + if("evolve") + var/datum/borer_evolution/to_evolve_path = text2path(params["path"]) + if(!ispath(to_evolve_path)) + CRASH("Cortical borer attempted to evolve with a non-evolution path! (Got: [to_evolve_path])") + + if(initial(to_evolve_path.evo_cost) > cortical_owner.stat_evolution) + return + if(initial(to_evolve_path.mutually_exclusive) && cortical_owner.genome_locked) + return + if(!cortical_owner.do_evolution(to_evolve_path)) + return + + cortical_owner.stat_evolution -= initial(to_evolve_path.evo_cost) + return TRUE + +/datum/action/cooldown/borer/evolution_tree/ui_state(mob/user) + return GLOB.always_state diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/force_speech.dm b/monkestation/code/modules/antagonists/borers/code/abilities/force_speech.dm new file mode 100644 index 000000000000..531220c4392f --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/force_speech.dm @@ -0,0 +1,31 @@ +/datum/action/cooldown/borer/force_speak + name = "Force Host Speak" + cooldown_time = 30 SECONDS + button_icon_state = "speak" + requires_host = TRUE + sugar_restricted = TRUE + ability_explanation = "\ + Forces your host to speak any words you desire.\ + " + +/datum/action/cooldown/borer/force_speak/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + var/borer_message = input(cortical_owner, "What would you like to force your host to say?", "Force Speak") as message|null + if(!borer_message) + owner.balloon_alert(owner, "no message given") + return + borer_message = sanitize(borer_message) + var/mob/living/carbon/human/cortical_host = cortical_owner.human_host + to_chat(cortical_host, span_boldwarning("Your voice moves without your permission!")) + var/obj/item/organ/internal/brain/victim_brain = cortical_owner.human_host.get_organ_slot(ORGAN_SLOT_BRAIN) + if(victim_brain) + cortical_owner.human_host.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2 * cortical_owner.host_harm_multiplier) + cortical_host.say(message = borer_message, forced = "borer ([key_name(cortical_owner)])") + var/turf/human_turf = get_turf(cortical_owner.human_host) + var/logging_text = "[key_name(cortical_owner)] forced [key_name(cortical_owner.human_host)] to say [borer_message] at [loc_name(human_turf)]" + cortical_owner.log_message(logging_text, LOG_GAME) + cortical_owner.human_host.log_message(logging_text, LOG_GAME) + StartCooldown() diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/hide_presence.dm b/monkestation/code/modules/antagonists/borers/code/abilities/hide_presence.dm new file mode 100644 index 000000000000..9dc7157e3d4f --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/hide_presence.dm @@ -0,0 +1,31 @@ +/datum/action/cooldown/borer/stealth_mode + name = "Stealth Mode" + cooldown_time = 2 MINUTES + button_icon_state = "hiding" + chemical_cost = 100 + sugar_restricted = TRUE + ability_explanation = "\ + Very effectivelly hides your presence\n\ + While in stealth, you will crawl onto people without any noticable signs nor warning\n\ + Additionally you will not have any negative effects onto your host, but wont generate internal chemicals\n\ + " + +/datum/action/cooldown/borer/stealth_mode/Trigger(trigger_flags, atom/target) + var/mob/living/basic/cortical_borer/cortical_owner = owner + var/in_stealth = (cortical_owner.upgrade_flags & BORER_STEALTH_MODE) + if(in_stealth) + chemical_cost = 0 + else + chemical_cost = initial(chemical_cost) + . = ..() + if(!.) + return FALSE + owner.balloon_alert(owner, "stealth mode [in_stealth ? "disabled" : "enabled"]") + cortical_owner.chemical_storage -= chemical_cost + if(in_stealth) + cortical_owner.upgrade_flags &= ~BORER_STEALTH_MODE + else + cortical_owner.upgrade_flags |= BORER_STEALTH_MODE + + + StartCooldown() diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/host_healthscan.dm b/monkestation/code/modules/antagonists/borers/code/abilities/host_healthscan.dm new file mode 100644 index 000000000000..df07155bea6a --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/host_healthscan.dm @@ -0,0 +1,19 @@ +/datum/action/cooldown/borer/check_blood + name = "Check Blood" + cooldown_time = 5 SECONDS + button_icon_state = "blood" + requires_host = TRUE + sugar_restricted = TRUE + ability_explanation = "\ + Allows you to check your host's health\n\ + Additionally you will be able to taste the host's chemicals and measure them acuratelly\n\ + " + +/datum/action/cooldown/borer/check_blood/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + healthscan(owner, cortical_owner.human_host, advanced = TRUE) // :thinking: + chemscan(owner, cortical_owner.human_host) + StartCooldown() diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/incite_fear.dm b/monkestation/code/modules/antagonists/borers/code/abilities/incite_fear.dm new file mode 100644 index 000000000000..1986ac9fa651 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/incite_fear.dm @@ -0,0 +1,63 @@ +/datum/action/cooldown/borer/fear_human + name = "Incite Fear" + cooldown_time = 12 SECONDS + button_icon_state = "fear" + sugar_restricted = TRUE + ability_explanation = "\ + Causes an extreme fear reaction in a person near you whilst outside of a host\n\ + While inside of a host, it is much more effective and is used on the host itself\n\ + " + +/datum/action/cooldown/borer/fear_human/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + if(cortical_owner.human_host) + incite_internal_fear() + StartCooldown() + return + var/list/potential_freezers = list() + for(var/mob/living/carbon/human/listed_human in range(1, cortical_owner)) + if(!ishuman(listed_human)) //no nonhuman hosts + continue + if(listed_human.stat == DEAD) //no dead hosts + continue + if(considered_afk(listed_human.mind)) //no afk hosts + continue + potential_freezers += listed_human + if(length(potential_freezers) == 1) + incite_fear(potential_freezers[1]) + return + var/mob/living/carbon/human/choose_fear = tgui_input_list(cortical_owner, "Choose who you will fear!", "Fear Choice", potential_freezers) + if(!choose_fear) + owner.balloon_alert(owner, "no target chosen") + return + if(get_dist(choose_fear, cortical_owner) > 1) + owner.balloon_alert(owner, "chosen target too far") + return + incite_fear(choose_fear) + StartCooldown() + +/datum/action/cooldown/borer/fear_human/proc/incite_fear(mob/living/carbon/human/singular_fear) + var/mob/living/basic/cortical_borer/cortical_owner = owner + to_chat(singular_fear, span_warning("Something glares menacingly at you!")) + singular_fear.Paralyze(7 SECONDS) + singular_fear.stamina.adjust(-50) + singular_fear.set_confusion_if_lower(9 SECONDS) + var/turf/human_turf = get_turf(singular_fear) + var/logging_text = "[key_name(cortical_owner)] feared/paralyzed [key_name(singular_fear)] at [loc_name(human_turf)]" + cortical_owner.log_message(logging_text, LOG_GAME) + singular_fear.log_message(logging_text, LOG_GAME) + +/datum/action/cooldown/borer/fear_human/proc/incite_internal_fear() + var/mob/living/basic/cortical_borer/cortical_owner = owner + owner.balloon_alert(owner, "fear incited into host") + cortical_owner.human_host.Paralyze(10 SECONDS) + cortical_owner.human_host.stamina.adjust(-100) + cortical_owner.human_host.set_confusion_if_lower(15 SECONDS) + to_chat(cortical_owner.human_host, span_warning("Something moves inside of you violently!")) + var/turf/human_turf = get_turf(cortical_owner.human_host) + var/logging_text = "[key_name(cortical_owner)] feared/paralyzed [key_name(cortical_owner.human_host)] (internal) at [loc_name(human_turf)]" + cortical_owner.log_message(logging_text, LOG_GAME) + cortical_owner.human_host.log_message(logging_text, LOG_GAME) diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/learn_chemicals.dm b/monkestation/code/modules/antagonists/borers/code/abilities/learn_chemicals.dm new file mode 100644 index 000000000000..74fcc3380f84 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/learn_chemicals.dm @@ -0,0 +1,145 @@ +/// If a borer learns this amount of chemicals from blood, it will count for their objective +#define BLOOD_CHEM_OBJECTIVE 3 + +/// How many chemicals does a borer need to count for the objective. We use this exclusivelly for text on the end-round-panel +GLOBAL_VAR_INIT(objective_blood_chem, 3) + +/// Whats the borers progress on getting the chemical objective done? +GLOBAL_VAR_INIT(successful_blood_chem, 0) + +/// How many borers should have to learn "objective_blood_chem" amount of chemicals before we count the objective as complete +GLOBAL_VAR_INIT(objective_blood_borer, 3) + +/** + * Lets borers learn pre-coded chemicals in the "potential_chemicals" list + */ +/datum/action/cooldown/borer/upgrade_chemical + name = "Learn New Chemical" + button_icon_state = "bloodlevel" + chemical_evo_points = 1 + requires_host = TRUE + sugar_restricted = TRUE + ability_explanation = "\ + Allows you to learn various unlocked chemicals\n\ + To expand the chemical choice you need to use the evolution ability\n\ + " + +/datum/action/cooldown/borer/upgrade_chemical/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + + if(!length(cortical_owner.potential_chemicals)) + owner.balloon_alert(owner, "all chemicals learned") + return + + // Give the chemicals we can learn all proper names instead of datum/chemical/whatever, and show that to the user + var/named_chemicals = list() + for(var/datum/reagent/learnable_chemical as anything in cortical_owner.potential_chemicals) + named_chemicals += initial(learnable_chemical.name) + + var/reagent_choice = tgui_input_list( + cortical_owner, + "Choose a chemical to learn.", + "Chemical Selection", + named_chemicals, + ) + if(!reagent_choice) + owner.balloon_alert(owner, "no chemical chosen") + return + + // We only know the chosen chemicals name at this point, so we gotta check what chemical do we actually give them + var/datum/reagent/learned_reagent + for(var/datum/reagent/chemical as anything in cortical_owner.potential_chemicals) + if(initial(chemical.name) == reagent_choice) + learned_reagent = chemical + + cortical_owner.known_chemicals += learned_reagent + cortical_owner.chemical_evolution -= chemical_evo_points + cortical_owner.potential_chemicals -= learned_reagent + + owner.balloon_alert(owner, "[reagent_choice] learned") + if(!HAS_TRAIT(cortical_owner.human_host, TRAIT_AGEUSIA)) + to_chat(cortical_owner.human_host, span_notice("You get a strange aftertaste of [initial(learned_reagent.taste_description)]!")) + + var/obj/item/organ/internal/brain/victim_brain = cortical_owner.human_host.get_organ_slot(ORGAN_SLOT_BRAIN) + if(victim_brain) + cortical_owner.human_host.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5 * cortical_owner.host_harm_multiplier) + + StartCooldown() + +/** + * Lets borers learn chemicals that the host they reside in currently possess unless its in the "blacklisted_chemicals" list + * This ability is required for one of the borer's objectives + */ +/datum/action/cooldown/borer/learn_bloodchemical + name = "Learn Chemical from Blood" + button_icon_state = "bloodchem" + chemical_evo_points = 5 + requires_host = TRUE + sugar_restricted = TRUE + ability_explanation = "\ + Allows you to learn chemicals from blood at a much steeper price\n\ + Does not work on certain chemicals whose mollecular complexity is too high\n\ + " + +/datum/action/cooldown/borer/learn_bloodchemical/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + + if(length(cortical_owner.human_host.reagents.reagent_list) <= 0) + owner.balloon_alert(owner, "no reagents in host") + return + + // Give the chemicals we can learn all proper names instead of datum/chemical/whatever, and show that to the user + var/named_chemicals = list() + for(var/datum/reagent/learnable_chemical as anything in cortical_owner.human_host.reagents.reagent_list) + named_chemicals += initial(learnable_chemical.name) + + var/reagent_choice = tgui_input_list( + cortical_owner, + "Choose a chemical to learn.", + "Chemical Selection", + named_chemicals, + ) + if(!reagent_choice) + owner.balloon_alert(owner, "no chemical chosen") + return + + // We only know the chosen chemicals name at this point, so we gotta check what chemical do we actually give them + var/datum/reagent/learned_reagent + for(var/datum/reagent/chemical as anything in cortical_owner.human_host.reagents.reagent_list) + if(initial(chemical.name) == reagent_choice) + learned_reagent = chemical + + if(locate(learned_reagent) in cortical_owner.known_chemicals) + owner.balloon_alert(owner, "chemical already known") + return + if(locate(learned_reagent) in cortical_owner.blacklisted_chemicals) + owner.balloon_alert(owner, "chemical blacklisted") + return + if(!(learned_reagent.chemical_flags & REAGENT_CAN_BE_SYNTHESIZED)) + owner.balloon_alert(owner, "cannot learn [reagent_choice]") + return + + cortical_owner.chemical_evolution -= chemical_evo_points + cortical_owner.known_chemicals += learned_reagent.type + cortical_owner.blood_chems_learned++ + + var/obj/item/organ/internal/brain/victim_brain = cortical_owner.human_host.get_organ_slot(ORGAN_SLOT_BRAIN) + if(victim_brain) + cortical_owner.human_host.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5 * cortical_owner.host_harm_multiplier) + + if(cortical_owner.blood_chems_learned == BLOOD_CHEM_OBJECTIVE) + GLOB.successful_blood_chem += 1 + + owner.balloon_alert(owner, "[reagent_choice] learned") + if(!HAS_TRAIT(cortical_owner.human_host, TRAIT_AGEUSIA)) + to_chat(cortical_owner.human_host, span_notice("You get a strange aftertaste of [initial(learned_reagent.taste_description)]!")) + + StartCooldown() + +#undef BLOOD_CHEM_OBJECTIVE diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/learn_focus.dm b/monkestation/code/modules/antagonists/borers/code/abilities/learn_focus.dm new file mode 100644 index 000000000000..612f0441d1cf --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/learn_focus.dm @@ -0,0 +1,36 @@ +/datum/action/cooldown/borer/learn_focus + name = "Learn Focus" + button_icon_state = "getfocus" + requires_host = TRUE + sugar_restricted = TRUE + ability_explanation = "\ + Lets you evolve strong passive modifiers into the hosts you inhabit\n\ + Your focuses will follow you when you leave your host\n\ + " + +/datum/action/cooldown/borer/learn_focus/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + if(!length(cortical_owner.possible_focuses)) + owner.balloon_alert(owner, "all focuses already learned") + return + var/list/fancy_list = list() + for(var/datum/borer_focus/foci as anything in cortical_owner.possible_focuses) + if(foci in cortical_owner.body_focuses) + continue + fancy_list["[foci.name] ([foci.cost] points)"] = foci + var/focus_choice = tgui_input_list(cortical_owner, "Learn a focus!", "Focus Choice", fancy_list) + if(!focus_choice) + owner.balloon_alert(owner, "focus not chosen") + return + var/datum/borer_focus/picked_focus = fancy_list[focus_choice] + if(cortical_owner.stat_evolution < picked_focus.cost) + owner.balloon_alert(owner, "[picked_focus.cost] points required") + return + cortical_owner.stat_evolution -= picked_focus.cost + cortical_owner.body_focuses += picked_focus + picked_focus.on_add(cortical_owner.human_host, owner) + owner.balloon_alert(owner, "focus learned successfully") + StartCooldown() diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/revive_host.dm b/monkestation/code/modules/antagonists/borers/code/abilities/revive_host.dm new file mode 100644 index 000000000000..58c2c536113a --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/revive_host.dm @@ -0,0 +1,45 @@ +//revive your host +/datum/action/cooldown/borer/revive_host + name = "Revive Host" + cooldown_time = 2 MINUTES + button_icon_state = "revive" + chemical_cost = 200 + requires_host = TRUE + sugar_restricted = TRUE + needs_dead_host = TRUE + ability_explanation = "\ + Halfs all the damage, including organ damage that your host has. Then defiblirates their heart\n\ + You may need to use this ability multiple times depending on how badly your host is damaged\n\ + " + +/datum/action/cooldown/borer/revive_host/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + + cortical_owner.chemical_storage -= chemical_cost + + if(cortical_owner.human_host.getBruteLoss()) + cortical_owner.human_host.adjustBruteLoss(-(cortical_owner.human_host.getBruteLoss()*0.5)) + if(cortical_owner.human_host.getToxLoss()) + cortical_owner.human_host.adjustToxLoss(-(cortical_owner.human_host.getToxLoss()*0.5)) + if(cortical_owner.human_host.getFireLoss()) + cortical_owner.human_host.adjustFireLoss(-(cortical_owner.human_host.getFireLoss()*0.5)) + if(cortical_owner.human_host.getOxyLoss()) + cortical_owner.human_host.adjustOxyLoss(-(cortical_owner.human_host.getOxyLoss()*0.5)) + + if(cortical_owner.human_host.blood_volume < BLOOD_VOLUME_BAD) + cortical_owner.human_host.blood_volume = BLOOD_VOLUME_BAD + + for(var/obj/item/organ/internal/internal_target in cortical_owner.human_host.organs) + internal_target.apply_organ_damage(-internal_target.damage * 0.5) + + cortical_owner.human_host.revive() + to_chat(cortical_owner.human_host, span_boldwarning("Your heart jumpstarts!")) + owner.balloon_alert(owner, "host revived") + var/turf/human_turf = get_turf(cortical_owner.human_host) + var/logging_text = "[key_name(cortical_owner)] revived [key_name(cortical_owner.human_host)] at [loc_name(human_turf)]" + cortical_owner.log_message(logging_text, LOG_GAME) + cortical_owner.human_host.log_message(logging_text, LOG_GAME) + StartCooldown() diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/spawn_offspring.dm b/monkestation/code/modules/antagonists/borers/code/abilities/spawn_offspring.dm new file mode 100644 index 000000000000..87b137f78851 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/spawn_offspring.dm @@ -0,0 +1,117 @@ +/// How much health do we take from the borer if its producing eggs without a host? +#define OUT_OF_HOST_EGG_COST 50 + +//we need a way to produce offspring +/datum/action/cooldown/borer/produce_offspring + name = "Produce Offspring" + cooldown_time = 1 MINUTES + button_icon_state = "reproduce" + chemical_cost = 100 + needs_living_host = TRUE + ability_explanation = "\ + Forces your host to produce a borer egg inside of their stomach, then vomit it up\n\ + Be carefull as the egg is fragile and can be broken very easily by any human, along with being extremelly noticable\n\ + " + +/datum/action/cooldown/borer/produce_offspring/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + if(cortical_owner.neutered == TRUE) + owner.balloon_alert(owner, "You cannot reproduce!") + return + if(!(cortical_owner.upgrade_flags & BORER_ALONE_PRODUCTION) && !cortical_owner.inside_human()) + owner.balloon_alert(owner, "host required") + return + cortical_owner.chemical_storage -= chemical_cost + if((cortical_owner.upgrade_flags & BORER_ALONE_PRODUCTION) && !cortical_owner.inside_human()) + no_host_egg() + StartCooldown() + return + produce_egg() + var/obj/item/organ/internal/brain/victim_brain = cortical_owner.human_host.get_organ_slot(ORGAN_SLOT_BRAIN) + if(victim_brain) + cortical_owner.human_host.adjustOrganLoss(ORGAN_SLOT_BRAIN, 25 * cortical_owner.host_harm_multiplier) + var/eggroll = rand(1,100) + if(eggroll <= 75) + switch(eggroll) + if(1 to 34) + cortical_owner.human_host.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_BASIC) + owner.balloon_alert(owner, "Cerebrum damaged!") + if(35 to 60) + cortical_owner.human_host.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_SURGERY) + owner.balloon_alert(owner, "Cerebellum damaged!") + if(61 to 71) + cortical_owner.human_host.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_SURGERY) + owner.balloon_alert(owner, "Brainstem damaged!") + if(72 to 75) + cortical_owner.human_host.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_LOBOTOMY) + owner.balloon_alert(owner, "Brainstem severelly damaged!") + to_chat(cortical_owner.human_host, span_warning("Your brain begins to hurt...")) + var/turf/borer_turf = get_turf(cortical_owner) + new /obj/effect/decal/cleanable/vomit(borer_turf) + playsound(borer_turf, 'sound/effects/splat.ogg', 50, TRUE) + var/logging_text = "[key_name(cortical_owner)] gave birth at [loc_name(borer_turf)]" + cortical_owner.log_message(logging_text, LOG_GAME) + owner.balloon_alert(owner, "egg laid") + StartCooldown() + +/datum/action/cooldown/borer/produce_offspring/proc/no_host_egg() + var/mob/living/basic/cortical_borer/cortical_owner = owner + cortical_owner.health = max(cortical_owner.health, 1, cortical_owner.health -= OUT_OF_HOST_EGG_COST) + produce_egg() + var/turf/borer_turf = get_turf(cortical_owner) + new/obj/effect/decal/cleanable/blood/splatter(borer_turf) + playsound(borer_turf, 'sound/effects/splat.ogg', 50, TRUE) + var/logging_text = "[key_name(cortical_owner)] gave birth alone at [loc_name(borer_turf)]" + cortical_owner.log_message(logging_text, LOG_GAME) + owner.balloon_alert(owner, "egg laid") + +/datum/action/cooldown/borer/produce_offspring/proc/produce_egg() + var/mob/living/basic/cortical_borer/cortical_owner = owner + var/obj/effect/mob_spawn/ghost_role/borer_egg/spawned_egg = new(cortical_owner.drop_location()) + spawned_egg.generation = (cortical_owner.generation + 1) + cortical_owner.children_produced++ + if(cortical_owner.children_produced == GLOB.objective_egg_egg_number) + GLOB.successful_egg_number += 1 + +#undef OUT_OF_HOST_EGG_COST + +/datum/action/cooldown/borer/empowered_offspring + name = "Produce Empowered Offspring" + cooldown_time = 1 MINUTES + button_icon_state = "reproduce" + chemical_cost = 150 + requires_host = TRUE + needs_dead_host = TRUE + ability_explanation = "\ + Implants an egg onto a dead host, the egg will take 3 minutes to hatch and will die if the host gets revived\n\ + If the egg hatches, a massivelly stronger than normal borer will be created. Surpassing all others.\n\ + " + +/datum/action/cooldown/borer/empowered_offspring/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + if(cortical_owner.neutered == TRUE) + owner.balloon_alert(owner, "You cannot reproduce!") + return + + cortical_owner.chemical_storage -= chemical_cost + var/turf/borer_turf = get_turf(cortical_owner) + var/obj/item/bodypart/chest/chest = cortical_owner.human_host.get_bodypart(BODY_ZONE_CHEST) + if((!chest || IS_ORGANIC_LIMB(chest)) && !cortical_owner.human_host.get_organ_by_type(/obj/item/organ/internal/empowered_borer_egg)) + var/obj/item/organ/internal/empowered_borer_egg/spawned_egg = new(cortical_owner.human_host) + spawned_egg.generation = (cortical_owner.generation + 1) + + cortical_owner.children_produced += 1 + if(cortical_owner.children_produced == GLOB.objective_egg_egg_number) + GLOB.successful_egg_number += 1 + + playsound(borer_turf, 'sound/effects/splat.ogg', 50, TRUE) + var/logging_text = "[key_name(cortical_owner)] gave birth to an empowered borer at [loc_name(borer_turf)]" + cortical_owner.log_message(logging_text, LOG_GAME) + cortical_owner.balloon_alert(owner, "egg laid") + StartCooldown() diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/toggle_stealth.dm b/monkestation/code/modules/antagonists/borers/code/abilities/toggle_stealth.dm new file mode 100644 index 000000000000..7cbef00ae148 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/toggle_stealth.dm @@ -0,0 +1,26 @@ +/datum/action/cooldown/borer/toggle_hiding + name = "Toggle Hiding" + button_icon_state = "hide" + var/hide_layer = ABOVE_NORMAL_TURF_LAYER + ability_explanation = "\ + Turns your hiding abilities on/off\n\ + Whilst on, you will hide under most objects, like tables.\n\ + If you are a diveworm, you will bore into hosts twice as fast whilst not hidden\n\ + " + +/datum/action/cooldown/borer/toggle_hiding/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + if(owner.layer != hide_layer) + cortical_owner.upgrade_flags |= BORER_HIDING + owner.balloon_alert(owner, "started hiding") + owner.layer = hide_layer + + else + cortical_owner.upgrade_flags &= ~BORER_HIDING + owner.balloon_alert(owner, "stopped hiding") + owner.layer = BELOW_MOB_LAYER + + StartCooldown() diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/upgrade_body.dm b/monkestation/code/modules/antagonists/borers/code/abilities/upgrade_body.dm new file mode 100644 index 000000000000..6648da839962 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/upgrade_body.dm @@ -0,0 +1,32 @@ +/datum/action/cooldown/borer/upgrade_stat + name = "Become Stronger" + button_icon_state = "level" + stat_evo_points = 1 + requires_host = TRUE + sugar_restricted = TRUE + ability_explanation = "\ + Lets you become stronger in exchange for an evolution point\n\ + Your maximum health, regeneration, chemical storage and chemical regeneration will all be faster\n\ + " + +/datum/action/cooldown/borer/upgrade_stat/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + + cortical_owner.stat_evolution -= stat_evo_points + cortical_owner.maxHealth += cortical_owner.health_per_level + cortical_owner.health_regen += cortical_owner.health_regen_per_level + cortical_owner.max_chemical_storage += cortical_owner.chem_storage_per_level + cortical_owner.chemical_regen += cortical_owner.chem_regen_per_level + cortical_owner.level += 1 + + var/obj/item/organ/internal/brain/victim_brain = cortical_owner.human_host.get_organ_slot(ORGAN_SLOT_BRAIN) + if(victim_brain) + cortical_owner.human_host.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10 * cortical_owner.host_harm_multiplier) + + cortical_owner.human_host.adjust_eye_blur(6 SECONDS * cortical_owner.host_harm_multiplier) //about 12 seconds' worth by default + to_chat(cortical_owner, span_notice("You have grown!")) + to_chat(cortical_owner.human_host, span_warning("You feel a sharp pressure in your head!")) + StartCooldown() diff --git a/monkestation/code/modules/antagonists/borers/code/abilities/willing_host.dm b/monkestation/code/modules/antagonists/borers/code/abilities/willing_host.dm new file mode 100644 index 000000000000..085d40b489e5 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/abilities/willing_host.dm @@ -0,0 +1,36 @@ +/datum/action/cooldown/borer/willing_host + name = "Willing Host" + cooldown_time = 2 MINUTES + button_icon_state = "willing" + chemical_cost = 150 + requires_host = TRUE + sugar_restricted = TRUE + ability_explanation = "\ + Asks your host if they accept your existance inside of them\n\ + If the host agrees, you will progress one of your objectives.\n\ + Whilst this does not immediatelly provide a benefit to you, enough willing hosts will make your evolution and chemical points accumulate quicker.\n\ + " + +/datum/action/cooldown/borer/willing_host/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/cortical_borer/cortical_owner = owner + for(var/ckey_check in GLOB.willing_hosts) + if(ckey_check == cortical_owner.human_host.ckey) + owner.balloon_alert(owner, "host already willing") + return + + owner.balloon_alert(owner, "asking host...") + cortical_owner.chemical_storage -= chemical_cost + + var/host_choice = tgui_input_list(cortical_owner.human_host,"Do you accept to be a willing host?", "Willing Host Request", list("Yes", "No")) + if(host_choice != "Yes") + owner.balloon_alert(owner, "host not willing!") + StartCooldown() + return + + owner.balloon_alert(owner, "host willing!") + to_chat(cortical_owner.human_host, span_notice("You have accepted being a willing host!")) + GLOB.willing_hosts += cortical_owner.human_host.ckey + StartCooldown() diff --git a/monkestation/code/modules/antagonists/borers/code/antagonist_stuff/antagonist_datum.dm b/monkestation/code/modules/antagonists/borers/code/antagonist_stuff/antagonist_datum.dm new file mode 100644 index 000000000000..06df30a9ef2c --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/antagonist_stuff/antagonist_datum.dm @@ -0,0 +1,128 @@ +/datum/antagonist/cortical_borer + name = "Cortical Borer" + job_rank = ROLE_CORTICAL_BORER + roundend_category = "enslaved cortical borers" // may look a bit confusing, but these borers are not a part of a hivemind. So they are probably enslaved + antagpanel_category = "Cortical Borers" + ui_name = "AntagInfoBorer" + count_against_dynamic_roll_chance = FALSE // there are thousands of them, we do not need them to be the only antagonist + prevent_roundtype_conversion = FALSE + show_to_ghosts = TRUE + /// Our linked borer, used for the antagonist panel TGUI + var/mob/living/basic/cortical_borer/cortical_owner + +/datum/antagonist/cortical_borer/antag_token(datum/mind/hosts_mind, mob/spender) + var/list/vents = list() + if(isliving(spender)) + hosts_mind.current.unequip_everything() + new /obj/effect/holy(hosts_mind.current.loc) + QDEL_IN(hosts_mind.current, 20) + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/atmospherics/components/unary/vent_pump)) + if(QDELETED(temp_vent)) + continue + if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) + var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] + if(!temp_vent_parent) + continue // No parent vent + // Stops Cortical Borers getting stuck in small networks. + // See: Security, Virology + if(length(temp_vent_parent.other_atmos_machines) > 20) + vents += temp_vent + + if(!length(vents)) + message_admins("Spawning in as a borer failed!") + return MAP_ERROR + + var/mob/dead/observer/new_borer = spender + var/turf/vent_turf = get_turf(pick(vents)) + var/mob/living/basic/cortical_borer/spawned_cb = new(vent_turf) + spawned_cb.ckey = new_borer.ckey + spawned_cb.mind.add_antag_datum(/datum/antagonist/cortical_borer/hivemind) + notify_ghosts( + "Someone has become a borer due to spending an antag token ([spawned_cb])!", + source = spawned_cb, + action = NOTIFY_ORBIT, + header = "Something's Interesting!", + ) + message_admins("[ADMIN_LOOKUPFLW(spawned_cb)] has been made into a borer by using an antag token.") + to_chat(spawned_cb, span_warning("You are a cortical borer! You can fear someone to make them stop moving, but make sure to inhabit them! You only grow/heal/talk when inside a host!")) + +/datum/antagonist/cortical_borer/on_gain() + cortical_owner = owner.current + forge_objectives() + return ..() + +/datum/antagonist/cortical_borer/get_preview_icon() + return finish_preview_icon(icon('monkestation/code/modules/antagonists/borers/icons/animal.dmi', "brainslug")) + +/datum/antagonist/cortical_borer/hivemind + roundend_category = "cortical borers" + /// The team of borers + var/datum/team/cortical_borers/borers + +/datum/antagonist/cortical_borer/hivemind/forge_objectives() + var/datum/objective/custom/borer_objective_produce_eggs = new + borer_objective_produce_eggs.explanation_text = "we require [GLOB.objective_egg_borer_number] different borers to produce [GLOB.objective_egg_egg_number] eggs to make sure our hive can spread widelly for increasing our chances of survival" + + var/datum/objective/custom/borer_objective_willing_hosts = new + borer_objective_willing_hosts.explanation_text = "we require any amount of the borers to get [GLOB.objective_willing_hosts] willing host's trust to ensure our survival" + + var/datum/objective/custom/borer_objective_learn_chemicals = new + borer_objective_learn_chemicals.explanation_text = "we require any amount of the borers to learn [GLOB.objective_blood_borer] chemicals from blood to aquire further chemical insight" + + objectives += borer_objective_produce_eggs + objectives += borer_objective_willing_hosts + objectives += borer_objective_learn_chemicals + +/datum/antagonist/cortical_borer/hivemind/create_team(datum/team/cortical_borers/new_team) + if(!new_team) + for(var/datum/antagonist/cortical_borer/hivemind/borer in GLOB.antagonists) + if(!borer.owner) + stack_trace("Antagonist datum without owner in GLOB.antagonists: [borer]") + continue + if(borer.borers) + borers = borer.borers + return + borers = new /datum/team/cortical_borers + return + if(!istype(new_team)) + stack_trace("Wrong team type passed to [type] initialization.") + borers = new_team + +/datum/antagonist/cortical_borer/hivemind/get_team() + return borers + +/datum/antagonist/cortical_borer/ui_static_data(mob/user) + var/list/data = list() + for(var/datum/action/cooldown/borer/ability as anything in cortical_owner.known_abilities) + var/list/ability_data = list() + + ability_data["ability_name"] = initial(ability.name) + ability_data["ability_explanation"] = initial(ability.ability_explanation) +/* Temporarily disabled -- Turn dis on once i figure out how to space stuff out properly in the TGUI + ability_data["ability_explanation"] += "Restrictions:" + if(ability.chemical_cost) + ability_data["ability_explanation"] += "

-To use this ability we need to use [ability.chemical_cost] of our internally synthesized chemicals. " + if(ability.stat_evo_points) + ability_data["ability_explanation"] += "-To make effective use of this ability we need to spend [ability.stat_evo_points] evolution points. " + if(ability.chemical_evo_points) + ability_data["ability_explanation"] += "-We have to use [ability.chemical_evo_points] chemical evolution points to use this ability. " + + if(ability.requires_host) + ability_data["ability_explanation"] += "-We require a host to use this ability. " + if(ability.needs_living_host) + ability_data["ability_explanation"] += "-Our host requires to be alive in order for us to use this ability. " + if(ability.needs_dead_host) + ability_data["ability_explanation"] += "-Our host must be deceased in order for us to make effective use of this ability. " + if(ability.sugar_restricted) + ability_data["ability_explanation"] += "-We cannot use this ability when our host is under the effect of a highly dangerous chemical known as \"sugar\". " +*/ + ability_data["ability_icon"] = initial(ability.button_icon_state) + + data["ability"] += list(ability_data) + + return data + ..() + +/datum/antagonist/cortical_borer/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/simple/borer_icons), + ) diff --git a/monkestation/code/modules/antagonists/borers/code/antagonist_stuff/midround_event.dm b/monkestation/code/modules/antagonists/borers/code/antagonist_stuff/midround_event.dm new file mode 100644 index 000000000000..a31808ed8eee --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/antagonist_stuff/midround_event.dm @@ -0,0 +1,124 @@ +/// How many people do we need per borer spawned +#define POP_PER_BORER 30 + +/datum/round_event_control/antagonist/solo/from_ghosts/cortical_borer + name = "Cortical Borer Infestation" + tags = list(TAG_TEAM_ANTAG, TAG_EXTERNAL, TAG_ALIEN) + typepath = /datum/round_event/ghost_role/cortical_borer + antag_flag = ROLE_CORTICAL_BORER + track = EVENT_TRACK_MAJOR + enemy_roles = list( + JOB_CAPTAIN, + JOB_DETECTIVE, + JOB_HEAD_OF_SECURITY, + JOB_SECURITY_OFFICER, + ) + required_enemies = 2 + weight = 5 // as rare as a natural blob + min_players = 20 + max_occurrences = 1 //should only ever happen once + dynamic_should_hijack = TRUE + category = EVENT_CATEGORY_ENTITIES + description = "A cortical borer has appeared on station. It will also attempt to produce eggs, and will attempt to gather willing hosts and learn chemicals through the blood." + +/datum/round_event/ghost_role/cortical_borer + announce_when = 400 + +/datum/round_event/ghost_role/cortical_borer/setup() + announce_when = rand(announce_when, announce_when + 50) + setup = TRUE + +/datum/round_event/ghost_role/cortical_borer/announce(fake) + priority_announce( + "Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", + "Lifesign Alert", + ANNOUNCER_ALIENS, + ) + +/datum/round_event/ghost_role/cortical_borer/start() + var/list/vents = list() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/atmospherics/components/unary/vent_pump)) + if(QDELETED(temp_vent)) + continue + if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) + var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] + if(!temp_vent_parent) + continue // No parent vent + // Stops Cortical Borers getting stuck in small networks. + // See: Security, Virology + if(length(temp_vent_parent.other_atmos_machines) > 20) + vents += temp_vent + + if(!length(vents)) + message_admins("An event attempted to spawn a borer but no suitable vents were found. Shutting down.") + return MAP_ERROR + + var/list/candidates = SSpolling.poll_ghost_candidates( + role = ROLE_CORTICAL_BORER, + ignore_category = POLL_IGNORE_CORTICAL_BORER, + pic_source = /mob/living/basic/cortical_borer, + ) + + if(!length(candidates)) + return NOT_ENOUGH_PLAYERS + + var/living_number = max(length(GLOB.player_list) / POP_PER_BORER, 1) + var/choosing_number = min(length(candidates), living_number) + + for(var/repeating_code in 1 to choosing_number) + var/mob/dead/observer/new_borer = pick(candidates) + candidates -= new_borer + var/turf/vent_turf = get_turf(pick(vents)) + var/mob/living/basic/cortical_borer/spawned_cb = new /mob/living/basic/cortical_borer(vent_turf) + spawned_cb.ckey = new_borer.ckey + spawned_cb.mind.add_antag_datum(/datum/antagonist/cortical_borer/hivemind) + announce_to_ghosts(spawned_cb) + message_admins("[ADMIN_LOOKUPFLW(spawned_cb)] has been made into a borer by an event.") + log_game("STORYTELLER: [key_name(new_borer)] was spawned as a borer by the storyteller.") + to_chat(spawned_cb, span_warning("You are a cortical borer! You can fear someone to make them stop moving, but make sure to inhabit them! You only grow/heal/talk when inside a host!")) + +/datum/dynamic_ruleset/midround/from_ghosts/cortical_borer + name = "Cortical Borer Infestation" + antag_datum = /datum/antagonist/cortical_borer + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY + antag_flag = ROLE_CORTICAL_BORER + enemy_roles = list( + JOB_CAPTAIN, + JOB_DETECTIVE, + JOB_HEAD_OF_SECURITY, + JOB_SECURITY_OFFICER, + ) + required_enemies = list(2,2,1,1,1,1,1,0,0,0) + required_candidates = 1 + weight = 3 + cost = 20 + minimum_players = 10 + /// List of on-station vents + var/list/vents = list() + +/datum/dynamic_ruleset/midround/from_ghosts/cortical_borer/execute() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/atmospherics/components/unary/vent_pump)) + if(QDELETED(temp_vent)) + continue + if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) + var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] + if(!temp_vent_parent) + continue // No parent vent + // Stops Borers getting stuck in small networks. + // See: Security, Virology + if(length(temp_vent_parent.other_atmos_machines) > 20) + vents += temp_vent + if(!length(vents)) + return FALSE + return TRUE + +/datum/dynamic_ruleset/midround/from_ghosts/cortical_borer/generate_ruleset_body(mob/applicant) + var/obj/vent = pick_n_take(vents) + var/mob/living/basic/cortical_borer/new_borer = new(vent.loc) + new_borer.key = applicant.key + new_borer.move_into_vent(vent) + message_admins("[ADMIN_LOOKUPFLW(new_borer)] has been made into a borer by the midround ruleset.") + log_game("DYNAMIC: [key_name(new_borer)] was spawned as a borer by the midround ruleset.") + return new_borer + +#undef POP_PER_BORER diff --git a/monkestation/code/modules/antagonists/borers/code/antagonist_stuff/round_end_text.dm b/monkestation/code/modules/antagonists/borers/code/antagonist_stuff/round_end_text.dm new file mode 100644 index 000000000000..0c8ba9a417bf --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/antagonist_stuff/round_end_text.dm @@ -0,0 +1,58 @@ +/proc/printborer(datum/mind/borer) + var/list/text = list() + var/mob/living/basic/cortical_borer/player_borer = borer.current + if(!player_borer) + text += span_redtext("[span_bold(borer.name)] had their body destroyed.") + return text.Join("
") + if(borer.current.stat != DEAD) + text += "[span_bold(player_borer.name)] [span_greentext("survived")]" + else + text += "[span_bold(player_borer.name)] [span_redtext("died")]" + text += span_bold("[span_bold(player_borer.name)] produced [player_borer.children_produced] borers.") + var/list/string_of_genomes = list() + + for(var/evo_index in player_borer.past_evolutions) + var/datum/borer_evolution/evolution = player_borer.past_evolutions[evo_index] + string_of_genomes += evolution.name + + text += "[span_bold(player_borer.name)] had the following evolutions: [english_list(string_of_genomes)]" + return text.Join("
") + +/proc/printborerlist(list/players,fleecheck) + var/list/parts = list() + + parts += "

" + return parts.Join("
") + +/datum/team/cortical_borers + name = "Cortical Borers" + +/datum/team/cortical_borers/roundend_report() + var/list/parts = list() + parts += span_header("The [name] were:") + parts += printborerlist(members) + var/survival = FALSE + for(var/mob/living/basic/cortical_borer/check_borer in GLOB.cortical_borers) + if(check_borer.stat == DEAD) + continue + survival = TRUE + if(survival) + parts += span_greentext("Borers were able to survive the shift!") + else + parts += span_redtext("Borers were unable to survive the shift!") + if(GLOB.successful_egg_number >= GLOB.objective_egg_borer_number) + parts += span_greentext("Borers were able to produce enough eggs!") + else + parts += span_redtext("Borers were unable to produce enough eggs!") + if(length(GLOB.willing_hosts) >= GLOB.objective_willing_hosts) + parts += span_greentext("Borers were able to gather enough willing hosts!") + else + parts += span_redtext("Borers were unable to gather enough willing hosts!") + if(GLOB.successful_blood_chem >= GLOB.objective_blood_borer) + parts += span_greentext("Borers were able to learn enough chemicals through the blood!") + else + parts += span_redtext("Borers were unable to learn enough chemicals through the blood!") + return "
[parts.Join("
")]
" diff --git a/monkestation/code/modules/antagonists/borers/code/cortical_borer_chems.dm b/monkestation/code/modules/antagonists/borers/code/cortical_borer_chems.dm new file mode 100644 index 000000000000..63a1cbdbb61d --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/cortical_borer_chems.dm @@ -0,0 +1,21 @@ +// Double the OD treshold, no brain damage +/datum/reagent/drug/methamphetamine/borer_version + name = "Unknown Methamphetamine Isomer" + overdose_threshold = 40 + +/datum/reagent/drug/methamphetamine/borer_version/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + var/high_message = pick("You feel hyper.", "You feel like you need to go faster.", "You feel like you can run the world.") + if(SPT_PROB(2.5, seconds_per_tick)) + to_chat(affected_mob, span_notice("[high_message]")) + affected_mob.add_mood_event("tweaking", /datum/mood_event/stimulant_medium, name) + affected_mob.AdjustStun(-40 * REM * seconds_per_tick) + affected_mob.AdjustKnockdown(-40 * REM * seconds_per_tick) + affected_mob.AdjustUnconscious(-40 * REM * seconds_per_tick) + affected_mob.AdjustParalyzed(-40 * REM * seconds_per_tick) + affected_mob.AdjustImmobilized(-40 * REM * seconds_per_tick) + affected_mob.stamina.adjust(2 * REM * seconds_per_tick, TRUE) + affected_mob.set_jitter_if_lower(4 SECONDS * REM * seconds_per_tick) + if(SPT_PROB(2.5, seconds_per_tick)) + affected_mob.emote(pick("twitch", "shiver")) + ..() + . = TRUE diff --git a/monkestation/code/modules/antagonists/borers/code/evolution/borer_evolution.dm b/monkestation/code/modules/antagonists/borers/code/evolution/borer_evolution.dm new file mode 100644 index 000000000000..ad8ce237db07 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/evolution/borer_evolution.dm @@ -0,0 +1,17 @@ +/mob/living/basic/cortical_borer/proc/get_possible_evolutions() + var/list/possible_evolutions = list() + for(var/evolution_index in past_evolutions) + var/datum/borer_evolution/evolution = past_evolutions[evolution_index] + possible_evolutions |= evolution.unlocked_evolutions + return possible_evolutions + +/mob/living/basic/cortical_borer/proc/do_evolution(datum/borer_evolution/evolution_type) + if(!ispath(evolution_type)) + stack_trace("[type] do_evolution was given an invalid path! (Got: [evolution_type])") + return FALSE + if(past_evolutions[evolution_type]) + return FALSE + var/datum/borer_evolution/initialized_evolution = new evolution_type() + past_evolutions[evolution_type] = initialized_evolution + initialized_evolution.on_evolve(src) + return TRUE diff --git a/monkestation/code/modules/antagonists/borers/code/evolution/evolution_datum.dm b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_datum.dm new file mode 100644 index 000000000000..ad9c32d1492f --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_datum.dm @@ -0,0 +1,52 @@ +/datum/borer_evolution + /// Name of the evolution + var/name = "" + /// Description of the evolution + var/desc = "" + /// Cost to get the evolution + var/evo_cost = 2 // T5 cost 3 points instead of 2 + /// Text to show the borer when they evolve + var/gain_text = "" + /// What evolution genome this is + var/evo_type = BORER_EVOLUTION_GENERAL + /// If TRUE, this is an evolution that locks out other evolutions of the same tier & above but in different genomes + var/mutually_exclusive = FALSE + /// What numerical tier is this? (Doesn't affect anything mechanically) + var/tier = 0 + + /// What evolutions this one unlocks + var/list/unlocked_evolutions = list() + /// What action does this evolution unlock + var/added_action = FALSE + /// If TRUE neutered borers wont be able to get an action button from this, but will still be able to progress through the evolution tree + var/restricted_for_neutered = FALSE + +/// What happens when a borer gets this evolution +/datum/borer_evolution/proc/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + SHOULD_CALL_PARENT(TRUE) + if(mutually_exclusive) + cortical_owner.genome_locked = TRUE + if(gain_text) + to_chat(cortical_owner, span_notice("[gain_text]")) + + if(restricted_for_neutered) + if(istype(cortical_owner, /mob/living/basic/cortical_borer/neutered)) + to_chat(cortical_owner, span_danger("You didnt manage to properly evolve, you feel a strange sensation.")) + return + if(added_action) + var/datum/action/cooldown/borer/new_action = new added_action(cortical_owner) + new_action.Grant(cortical_owner) + +/datum/borer_evolution/base + name = "The Beginning" + desc = "The start of a great age." + gain_text = "The worms, which we came to call \"Cortical Borers\", are fascinating creatures." + evo_cost = 0 + evo_type = BORER_EVOLUTION_START + tier = 0 + unlocked_evolutions = list( + /datum/borer_evolution/upgrade_injection, + /datum/borer_evolution/symbiote/willing_host, + /datum/borer_evolution/hivelord/produce_offspring, + /datum/borer_evolution/diveworm/health_per_level, + ) diff --git a/monkestation/code/modules/antagonists/borers/code/evolution/evolution_diveworm.dm b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_diveworm.dm new file mode 100644 index 000000000000..e5347cedf9e6 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_diveworm.dm @@ -0,0 +1,107 @@ +/datum/borer_evolution/diveworm + evo_type = BORER_EVOLUTION_DIVEWORM + +// T1 +/datum/borer_evolution/diveworm/health_per_level + name = "Health Increase" + desc = "Increase the amount of health per level-up you gain." + gain_text = "Over time, some of the more aggressive worms became harder to dissect post-mortem. Their skin membrane has become up to thrice as thick." + tier = 1 + unlocked_evolutions = list(/datum/borer_evolution/diveworm/host_speed) + evo_cost = 1 + +/datum/borer_evolution/diveworm/health_per_level/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.health_per_level += 2.5 + cortical_owner.recalculate_stats() + +// T2 +/datum/borer_evolution/diveworm/host_speed + name = "Boring Speed" + desc = "Decrease the time it takes to enter a host when you are not hiding." + gain_text = "Once or twice, I would blink, and see the non-host monkeys be grappling with a worm that was cross the room just moments before." + tier = 2 + unlocked_evolutions = list(/datum/borer_evolution/diveworm/expanded_chemicals) + +/datum/borer_evolution/diveworm/host_speed/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.upgrade_flags |= BORER_FAST_BORING + +// T3 + T1 path +/datum/borer_evolution/diveworm/expanded_chemicals + name = "Expanded Chemical List" + desc = "Gain access to a new list of devious chemicals to the unlockable list." + gain_text = "Sometimes, I would just see a known host monkey... collapse, then get up, then collapse again. It was as if the worm was playing with it..." + mutually_exclusive = TRUE + tier = 3 + unlocked_evolutions = list( + /datum/borer_evolution/diveworm/harm_increase, + /datum/borer_evolution/diveworm/health_per_level/t2, + ) + var/static/list/added_chemicals = list( + /datum/reagent/toxin/fentanyl, + /datum/reagent/toxin/staminatoxin, + /datum/reagent/toxin/mutetoxin, + /datum/reagent/toxin/mutagen, + /datum/reagent/toxin/cyanide, + /datum/reagent/drug/mushroomhallucinogen, + /datum/reagent/inverse/oculine, + ) + +/datum/borer_evolution/diveworm/expanded_chemicals/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.potential_chemicals |= added_chemicals + +/datum/borer_evolution/diveworm/health_per_level/t2 + name = "Health Increase II" + tier = -1 + unlocked_evolutions = list(/datum/borer_evolution/diveworm/health_per_level/t3) + evo_cost = 2 + +/datum/borer_evolution/diveworm/health_per_level/t3 + name = "Health Increase III" + tier = -1 + unlocked_evolutions = list() + evo_cost = 2 + +// T4 + its path +/datum/borer_evolution/diveworm/harm_increase + name = "Toxins Increase" + desc = "Increase the passive and active damage you do to your host, and how often it occurs." + gain_text = "In captivity, some of the worms became more... brutish, larger. Most notably, hosts succumbed much quicker to them." + tier = 4 + unlocked_evolutions = list( + /datum/borer_evolution/diveworm/harm_increase/t2, + /datum/borer_evolution/diveworm/empowered_offspring, + ) + +/datum/borer_evolution/diveworm/harm_increase/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.host_harm_multiplier += 0.25 + +/datum/borer_evolution/diveworm/harm_increase/t2 + name = "Toxins Increase II" + desc = "Further increase the passive and active damage you do to your host, and how often it occurs." + tier = -1 + unlocked_evolutions = list(/datum/borer_evolution/diveworm/harm_increase/t3) + +/datum/borer_evolution/diveworm/harm_increase/t3 + name = "Toxins Increase III" + desc = "Further increase the passive and active damage you do to your host, and how often it occurs." + tier = -1 + unlocked_evolutions = list() + +// T5 +/datum/borer_evolution/diveworm/empowered_offspring + name = "Empowered Offspring" + desc = "Lay an egg in a deceased host, and after a delay an empowered borer will burst out." + gain_text = "Most eggs would be regurgitated through the throat from their hosts... but one did not. They exploded out the chest like a horror movie. What a worrying discovery." + evo_cost = 3 + tier = 5 + unlocked_evolutions = list( + /datum/borer_evolution/sugar_immunity, + /datum/borer_evolution/synthetic_borer, + /datum/borer_evolution/synthetic_chems_negative, + ) + added_action = /datum/action/cooldown/borer/empowered_offspring + restricted_for_neutered = TRUE diff --git a/monkestation/code/modules/antagonists/borers/code/evolution/evolution_general.dm b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_general.dm new file mode 100644 index 000000000000..a5245087977d --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_general.dm @@ -0,0 +1,78 @@ +/datum/borer_evolution/upgrade_injection + name = "Upgrade Injection" + desc = "Upgrade your possible injection amount to 10 units." + gain_text = "Their growth is astounding, their organs and glands can expand several times their size in mere days." + unlocked_evolutions = list(/datum/borer_evolution/upgrade_injection/t2) + tier = 1 + +/datum/borer_evolution/upgrade_injection/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.injection_rates_unlocked += cortical_owner.injection_rates[length(cortical_owner.injection_rates_unlocked) + 1] + +/datum/borer_evolution/upgrade_injection/t2 + name = "Upgrade Injection II" + desc = "Upgrade your possible injection amount to 25 units." + unlocked_evolutions = list(/datum/borer_evolution/upgrade_injection/t3) + tier = 2 + +/datum/borer_evolution/upgrade_injection/t3 + name = "Upgrade Injection III" + desc = "Upgrade your possible injection amount to 50 units." + unlocked_evolutions = list() + tier = 3 + +/datum/borer_evolution/sugar_immunity + name = "Sugar Immunity" + desc = "Become immune to the ill effects of sugar in you or a host." + gain_text = "Of the biggest ones, a few have managed to resist the effects of sugar. Truly concerning if we wish to keep them contained." + evo_cost = 5 + tier = 6 + +/datum/borer_evolution/sugar_immunity/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.upgrade_flags |= BORER_SUGAR_IMMUNE + +/datum/borer_evolution/synthetic_borer + name = "Synthetic Boring" + desc = "Gain the ability to take synthetic humans as a host as well." + gain_text = "Now, we used robots to take care of the worms when they're alive, but one day... they all went haywire. Security took them down, closer inspection showed that the worms managed their way into the processing units." + evo_cost = 6 + tier = 6 + +/datum/borer_evolution/synthetic_borer/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.organic_restricted = FALSE + +/datum/borer_evolution/synthetic_chems_positive + name = "Synthetic Chemicals (+)" + desc = "Gain access to a list of helpful, synthetic-compatible chemicals." + gain_text = "Once we had established that robots weren't safe either, we began to experiment with them. Interestingly enough, some of them never needed to be oiled again." + tier = 6 + evo_cost = 6 + var/static/list/added_chemicals = list( + /datum/reagent/medicine/system_cleaner, + /datum/reagent/medicine/liquid_solder, + /datum/reagent/fuel/oil, + /datum/reagent/fuel, + ) + +/datum/borer_evolution/synthetic_chems_positive/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.potential_chemicals |= added_chemicals + +/datum/borer_evolution/synthetic_chems_negative + name = "Synthetic Chemicals (-)" + desc = "Gain access to a list of synthetic-damaging chemicals." + gain_text = "Good thing is, some of the worms were hostile to the robots, too. Corroded from the inside, some of them were basically husks." + tier = 6 + evo_cost = 6 + var/static/list/added_chemicals = list( + /datum/reagent/toxin/acid/fluacid, // More like anti everything but :shrug: + /datum/reagent/thermite, + /datum/reagent/pyrosium, + /datum/reagent/oxygen, + ) + +/datum/borer_evolution/synthetic_chems_negative/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.potential_chemicals |= added_chemicals diff --git a/monkestation/code/modules/antagonists/borers/code/evolution/evolution_hivelord.dm b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_hivelord.dm new file mode 100644 index 000000000000..c4b729eaf6a7 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_hivelord.dm @@ -0,0 +1,63 @@ +/datum/borer_evolution/hivelord + evo_type = BORER_EVOLUTION_HIVELORD + +// T1 +/datum/borer_evolution/hivelord/produce_offspring + name = "Produce Offspring" + desc = "Produce an egg, which your host will vomit up." + gain_text = "The way that a Cortical Borer produces an egg is a strange one. So far, we have not seen how it produces one, or it doing so outside a host." + tier = 1 + evo_cost = 1 + unlocked_evolutions = list(/datum/borer_evolution/hivelord/blood_chemical) + added_action = /datum/action/cooldown/borer/produce_offspring + restricted_for_neutered = TRUE + +// T2 +/datum/borer_evolution/hivelord/blood_chemical + name = "Learn Blood Chemical" + desc = "Learn a synthesizable chemical from the blood of your host." + gain_text = "As we were dissecting a former host monkey's fecal matter, I noticed a high concentration of banana matter, despite us not feeding them any for the past week." + tier = 2 + unlocked_evolutions = list(/datum/borer_evolution/hivelord/movespeed) + added_action = /datum/action/cooldown/borer/learn_bloodchemical + +// T3 +/datum/borer_evolution/hivelord/movespeed + name = "Increased Energy" + desc = "Boost your speed by a large amount." + gain_text = "And as I watched, the Cortical Borer was able to complete the course in just over half the time it had last week." + mutually_exclusive = TRUE + tier = 3 + unlocked_evolutions = list(/datum/borer_evolution/hivelord/stealth_mode) + +/datum/borer_evolution/hivelord/movespeed/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.add_movespeed_modifier(/datum/movespeed_modifier/borer_speed) + +// T4 +/datum/borer_evolution/hivelord/stealth_mode + name = "Stealth Mode" + desc = "While in stealth mode, your presence is much less noticable in hosts, but you do not gain passive benefits." + gain_text = "As I was writing my report one day, I noticed that one of the worms had slipped out of its cage and into a monkey without so much as a sound. Fascinating how they seem to know the importance of sound." + tier = 4 + unlocked_evolutions = list(/datum/borer_evolution/hivelord/produce_offspring_alone) + added_action = /datum/action/cooldown/borer/stealth_mode + +// T5 +/datum/borer_evolution/hivelord/produce_offspring_alone + name = "Produce Offspring II" + desc = "Allows you to produce eggs outside a host, in exchange for health and chemicals." + gain_text = "One of the worms seems to have taken an... Alpha position in the hive, producing more eggs than the others. Most worryingly, eggs have shown up without them having a host, but I haven't *seen* them lay any..." + evo_cost = 3 + tier = 5 + unlocked_evolutions = list( + /datum/borer_evolution/sugar_immunity, + /datum/borer_evolution/synthetic_borer, + /datum/borer_evolution/synthetic_chems_positive, + /datum/borer_evolution/synthetic_chems_negative, + ) + restricted_for_neutered = TRUE // we set this to TRUE purelly for the message, they dont have the reproduction action anyway + +/datum/borer_evolution/hivelord/produce_offspring_alone/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.upgrade_flags |= BORER_ALONE_PRODUCTION diff --git a/monkestation/code/modules/antagonists/borers/code/evolution/evolution_symbiote.dm b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_symbiote.dm new file mode 100644 index 000000000000..a5807b8d0060 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/evolution/evolution_symbiote.dm @@ -0,0 +1,103 @@ +/datum/borer_evolution/symbiote + evo_type = BORER_EVOLUTION_SYMBIOTE + +// T1 +/datum/borer_evolution/symbiote/willing_host + name = "Willing Host" + desc = "Ask a host if they are willing, furthering your objectives." + gain_text = "Some of the monkeys we gave the worms seemed far more... willing than others to be a host. I could've sworn one let them climb up their arm." + tier = 1 + unlocked_evolutions = list(/datum/borer_evolution/symbiote/chem_per_level) + evo_cost = 1 + added_action = /datum/action/cooldown/borer/willing_host + +// T2 +/datum/borer_evolution/symbiote/chem_per_level + name = "Chemical Increase" + desc = "Increase the amount of chemicals per level-up you gain." + gain_text = "The rate of which we've had to clean the borer pens is increasing. Perhaps their secretions are excess chemicals they cannot use?" + tier = 2 + unlocked_evolutions = list(/datum/borer_evolution/symbiote/expanded_chemicals) + +/datum/borer_evolution/symbiote/chem_per_level/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.chem_storage_per_level += 10 + cortical_owner.chem_regen_per_level += 0.5 + cortical_owner.recalculate_stats() + +// T3 + T2 Path +/datum/borer_evolution/symbiote/expanded_chemicals + name = "Expanded Chemical List" + desc = "Gain access to a new list of helpful chemicals to the unlockable list." + gain_text = "The chemicals the worms seem capable of synthesizing are truly remarkable, their hosts are able to get up from amazing amounts of harm." + mutually_exclusive = TRUE + tier = 3 + unlocked_evolutions = list( + /datum/borer_evolution/symbiote/harm_decrease, + /datum/borer_evolution/symbiote/chem_per_level/t2, + ) + var/static/list/added_chemicals = list( + /datum/reagent/medicine/sal_acid, + /datum/reagent/medicine/oxandrolone, + /datum/reagent/medicine/atropine, + /datum/reagent/medicine/neurine, + /datum/reagent/medicine/leporazine, + /datum/reagent/medicine/omnizine, + ) + +/datum/borer_evolution/symbiote/expanded_chemicals/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.potential_chemicals |= added_chemicals + +/datum/borer_evolution/symbiote/chem_per_level/t2 + name = "Chemical Increase II" + desc = "Increase the amount of chemicals per level-up you gain even further." + tier = -1 + unlocked_evolutions = list(/datum/borer_evolution/symbiote/chem_per_level/t3) + +/datum/borer_evolution/symbiote/chem_per_level/t3 + name = "Chemical Increase III" + desc = "Increase the amount of chemicals per level-up you gain even further." + tier = -1 + unlocked_evolutions = list() + +// T4 and path +/datum/borer_evolution/symbiote/harm_decrease + name = "Toxins Decrease" + desc = "Decrease the passive and active damage you do to your host, and how often it occurs." + gain_text = "However, some of the others became... if not smaller, certainly longer, more lithe." + tier = 4 + unlocked_evolutions = list( + /datum/borer_evolution/symbiote/harm_decrease/t2, + /datum/borer_evolution/symbiote/revive_host, + ) + +/datum/borer_evolution/symbiote/harm_decrease/on_evolve(mob/living/basic/cortical_borer/cortical_owner) + . = ..() + cortical_owner.host_harm_multiplier -= 0.25 + +/datum/borer_evolution/symbiote/harm_decrease/t2 + name = "Toxins Decrease II" + desc = "Further decrease the passive and active damage you do to your host, and how often it occurs." + tier = -1 + unlocked_evolutions = list(/datum/borer_evolution/symbiote/harm_decrease/t3) + +/datum/borer_evolution/symbiote/harm_decrease/t3 + name = "Toxins Decrease III" + desc = "Further decrease the passive and active damage you do to your host, and how often it occurs." + tier = -1 + unlocked_evolutions = list() + +// T5 +/datum/borer_evolution/symbiote/revive_host + name = "Revive Host" + desc = "Revive your host and heal what ails them." + gain_text = "As I was in the lab, the most curious occurance so far happened. A Cortical Borer went into one of the cadaver's heads, and moments later they were standing again." + evo_cost = 3 + tier = 5 + unlocked_evolutions = list( + /datum/borer_evolution/sugar_immunity, + /datum/borer_evolution/synthetic_borer, + /datum/borer_evolution/synthetic_chems_positive, + ) + added_action = /datum/action/cooldown/borer/revive_host diff --git a/monkestation/code/modules/antagonists/borers/code/focus_datum.dm b/monkestation/code/modules/antagonists/borers/code/focus_datum.dm new file mode 100644 index 000000000000..61f2bf648bb9 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/focus_datum.dm @@ -0,0 +1,78 @@ +/datum/borer_focus + /// Name of the focus + var/name = "" + /// Cost of the focus + var/cost = 5 + /// Traits to add/remove + var/list/traits = list() + /// Text that we send to the host when we give them a focus, if set + var/gain_text = FALSE + /// Text that we send to the host when the host loses a focus, if set + var/lose_text = FALSE + +/// Effects to take when the focus is added +/datum/borer_focus/proc/on_add(mob/living/carbon/human/host, mob/living/basic/cortical_borer/borer) + SHOULD_CALL_PARENT(TRUE) + if(gain_text) + to_chat(host, span_notice("[gain_text]")) + for(var/trait in traits) + ADD_TRAIT(host, trait, REF(borer)) + +/// Effects to take when the focus is removed +/datum/borer_focus/proc/on_remove(mob/living/carbon/human/host, mob/living/basic/cortical_borer/borer) + SHOULD_CALL_PARENT(TRUE) + if(lose_text) + to_chat(host, span_notice("[lose_text]")) + REMOVE_TRAITS_IN(host, REF(borer)) + +/datum/borer_focus/head + name = "head focus" + traits = list(TRAIT_NOFLASH, TRAIT_TRUE_NIGHT_VISION, TRAIT_KNOW_ENGI_WIRES) + gain_text = "Your eyes begin to feel strange..." + lose_text = "Your eyes begin to return to normal..." + +/datum/borer_focus/head/on_add(mob/living/carbon/human/host, mob/living/basic/cortical_borer/borer) + host.update_sight() + return ..() + +/datum/borer_focus/head/on_remove(mob/living/carbon/human/host, mob/living/basic/cortical_borer/borer) + host.update_sight() + return ..() + +/datum/borer_focus/chest + name = "chest focus" + traits = list(TRAIT_NOBREATH, TRAIT_NOHUNGER, TRAIT_STABLEHEART) + gain_text = "Your chest begins to slow down..." + lose_text = "Your chest begins to heave again..." + +/datum/borer_focus/chest/on_add(mob/living/carbon/human/host, mob/living/basic/cortical_borer/borer) + host.set_safe_hunger_level() + return ..() + +/datum/borer_focus/arms + name = "arm focus" + traits = list(TRAIT_QUICKER_CARRY, TRAIT_QUICK_BUILD, TRAIT_SHOCKIMMUNE) + gain_text = "Your arms start to feel funny..." + lose_text = "Your arms start to feel normal again..." + +/datum/borer_focus/arms/on_add(mob/living/carbon/human/host, mob/living/basic/cortical_borer/borer) + borer.human_host.add_actionspeed_modifier(/datum/actionspeed_modifier/focus_speed) + return ..() + +/datum/borer_focus/arms/on_remove(mob/living/carbon/human/host, mob/living/basic/cortical_borer/borer) + borer.human_host.remove_actionspeed_modifier(ACTIONSPEED_ID_BORER) + return ..() + +/datum/borer_focus/legs + name = "leg focus" + traits = list(TRAIT_LIGHT_STEP, TRAIT_FREERUNNING, TRAIT_SILENT_FOOTSTEPS) + gain_text = "You feel faster..." + lose_text = "You feel slower..." + +/datum/borer_focus/legs/on_add(mob/living/carbon/human/host, mob/living/basic/cortical_borer/borer) + host.add_movespeed_modifier(/datum/movespeed_modifier/focus_speed) + return ..() + +/datum/borer_focus/legs/on_remove(mob/living/carbon/human/host, mob/living/basic/cortical_borer/borer) + host.remove_movespeed_modifier(/datum/movespeed_modifier/focus_speed) + return ..() diff --git a/monkestation/code/modules/antagonists/borers/code/items/borer_spawner.dm b/monkestation/code/modules/antagonists/borers/code/items/borer_spawner.dm new file mode 100644 index 000000000000..808ff189bd0c --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/items/borer_spawner.dm @@ -0,0 +1,95 @@ +/obj/item/neutered_borer_spawner + name = "syndicate cortical borer cage" + desc = "The opposite of a harmless cage that is intended to capture cortical borer, \ + as this one contains a borer trained to assist anyone who it first sees in completing their goals." + icon = 'monkestation/code/modules/antagonists/borers/icons/items.dmi' + icon_state = "cage" + /// Used to animate the cage opening when you use the borer spawner, and closing if it fails to spawn a borer. Also midly against spam + var/opened = FALSE + /// Toggles if the borer spawner should be delayed or not, if this gets a value if will use that value to delay (for example: 5 SECONDS) + var/delayed = FALSE + /// Dictates the poll time + var/polling_time = 10 SECONDS + +/obj/item/neutered_borer_spawner/Initialize(mapload) + . = ..() + update_appearance() + +/obj/item/neutered_borer_spawner/update_overlays() + . = ..() + . += "borer" + if(opened) + . += "doors_open" + else + . += "doors_closed" + +/obj/item/neutered_borer_spawner/proc/do_wriggler_messages() + if(!opened) // there were no candidates at all somehow, probably tests on local. Lets not give messages after the fail message comes up + return + sleep(polling_time * 0.2) + visible_message(span_notice("The borer seems to have woken up")) + if(!opened) // one more check to be sure + return + sleep(polling_time * 0.2) + visible_message(span_notice("The borer has perked up their head, finally noticing the opened cage...")) + sleep(polling_time * 0.2) + visible_message(span_notice("The borer seems to slither cautiously to the cage entrance...")) + sleep(polling_time * 0.1) + visible_message(span_notice("The borer's head peeks outside of the cage...")) + +/obj/item/neutered_borer_spawner/attack_self(mob/living/user) + if(opened) + return + user.visible_message("[user] opens [src].", "You have opened the [src], awaiting for the borer to come out.", "You hear a metallic thunk.") + opened = TRUE + playsound(src, 'sound/machines/boltsup.ogg', 30, TRUE) + if(delayed) + sleep(delayed) + INVOKE_ASYNC(src, PROC_REF(do_wriggler_messages)) // give them something to look at whilst we poll the ghosts + update_appearance() + var/list/candidates = SSpolling.poll_ghost_candidates( + role = ROLE_CORTICAL_BORER, + poll_time = polling_time, + ignore_category = POLL_IGNORE_CORTICAL_BORER, + pic_source = /mob/living/basic/cortical_borer/neutered, + ) + if(!LAZYLEN(candidates)) + opened = FALSE + to_chat(user, "Yet the borer after looking at you quickly retreats back into their cage, visibly scared. Perhaps try later?") + playsound(src, 'sound/machines/boltsup.ogg', 30, TRUE) + update_appearance() + return + + var/mob/dead/observer/picked_candidate = pick(candidates) + + var/mob/living/basic/cortical_borer/neutered/new_mob = new(drop_location()) + new_mob.ckey = picked_candidate.ckey + + var/datum/antagonist/cortical_borer/borer_antagonist_datum = new + + var/datum/objective/protect/protect_objective = new + var/datum/objective/custom/listen_objective = new + + protect_objective.target = user.mind + protect_objective.update_explanation_text() + + listen_objective.explanation_text = "Listen to any commands given by [user.name]" + listen_objective.completed = TRUE // its just an objective for flavor less-so than for greentext + + borer_antagonist_datum.objectives += protect_objective + borer_antagonist_datum.objectives += listen_objective + + new_mob.mind.add_antag_datum(borer_antagonist_datum) + + notify_ghosts( + "[new_mob] has been chosen from the ghost pool!", + source = new_mob, + action = NOTIFY_ORBIT, + header = "Someone just got a new friend!" + ) + message_admins("[ADMIN_LOOKUPFLW(new_mob)] has been made into a borer via a traitor item used by [user]") + log_game("[key_name(new_mob)] was spawned as a borer by [key_name(user)]") + visible_message("A borer wriggles out of the [src]!") + + new /obj/item/cortical_cage(drop_location()) + qdel(src) diff --git a/monkestation/code/modules/antagonists/borers/code/items/egg.dm b/monkestation/code/modules/antagonists/borers/code/items/egg.dm new file mode 100644 index 000000000000..d7b3d5417adc --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/items/egg.dm @@ -0,0 +1,83 @@ +/obj/item/borer_egg + name = "borer egg" + desc = "An egg of a creature that is known to crawl inside of you, be careful." + icon = 'monkestation/code/modules/antagonists/borers/icons/animal.dmi' + icon_state = "brainegg" + layer = BELOW_MOB_LAYER + ///the spawner that is attached to this item + var/obj/effect/mob_spawn/ghost_role/borer_egg/host_spawner + +/obj/effect/mob_spawn/ghost_role/borer_egg + name = "borer egg" + desc = "An egg of a creature that is known to crawl inside of you, be careful." + icon = 'monkestation/code/modules/antagonists/borers/icons/animal.dmi' + icon_state = "brainegg" + layer = BELOW_MOB_LAYER + density = FALSE + mob_name = "cortical borer" + ///Type of mob that will be spawned + mob_type = /mob/living/basic/cortical_borer + role_ban = ROLE_CORTICAL_BORER + show_flavor = TRUE + prompt_name = "cortical borer" + you_are_text = "You are a Cortical Borer." + flavour_text = "You are a cortical borer! You can fear someone to make them stop moving, but make sure to inhabit them! \ + You only grow/heal/talk when inside a host!" + important_text = "As a borer, you have the option to be friendly or not. \n\ + Note that how you act will determine how a host responds. \n\ + Do not wordlessly resort to mechanics within a host. \n\ + You can talk to other borers using ; and your host by just speaking normally. \n\ + You are unable to speak outside of a host, but are able to emote." + ///what the generation of the borer egg is + var/generation = 0 + ///the egg that is attached to this mob spawn + var/obj/item/borer_egg/host_egg = /obj/item/borer_egg + +/obj/effect/mob_spawn/ghost_role/borer_egg/Destroy() + host_egg = null + return ..() + +/obj/effect/mob_spawn/ghost_role/borer_egg/special(mob/living/spawned_mob, mob/mob_possessor) + . = ..() + var/mob/living/basic/cortical_borer/cortical_mob = spawned_mob + cortical_mob.generation = generation + cortical_mob.mind.add_antag_datum(/datum/antagonist/cortical_borer/hivemind) + cortical_mob.create_name() + QDEL_NULL(host_egg) + +/obj/effect/mob_spawn/ghost_role/borer_egg/Initialize(mapload, datum/team/cortical_borers/borer_team) + . = ..() + host_egg = new(drop_location()) + host_egg.host_spawner = src + forceMove(host_egg) + var/area/src_area = get_area(src) + if(src_area) + notify_ghosts( + "A cortical borer egg has been laid in \the [src_area.name].", + source = src, + action = NOTIFY_PLAY, + flashwindow = FALSE, + ignore_key = POLL_IGNORE_DRONE, + notify_suiciders = FALSE, + ) + +/obj/item/borer_egg/attack_ghost(mob/user) + if(host_spawner) + host_spawner.attack_ghost(user) + return ..() + +/obj/item/borer_egg/attack_self(mob/user, modifiers) + to_chat(user, span_notice("You crush [src] within your grasp.")) + new /obj/effect/decal/cleanable/food/egg_smudge(user.drop_location()) + if(host_spawner) + QDEL_NULL(host_spawner) + qdel(src) + +/obj/item/borer_egg/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if (..()) // was it caught by a mob? + return + + var/turf/hit_turf = get_turf(hit_atom) + new /obj/effect/decal/cleanable/food/egg_smudge(hit_turf) + QDEL_NULL(host_spawner) + qdel(src) diff --git a/monkestation/code/modules/antagonists/borers/code/items/empowered_egg.dm b/monkestation/code/modules/antagonists/borers/code/items/empowered_egg.dm new file mode 100644 index 000000000000..8483c2e0ecd6 --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/items/empowered_egg.dm @@ -0,0 +1,66 @@ +/obj/item/borer_egg/empowered + name = "empowered borer egg" + icon_state = "empowered_brainegg" + +/obj/effect/mob_spawn/ghost_role/borer_egg/empowered + name = "empowered borer egg" + desc = "An egg of a creature that came crawling out of someone instead of into them." + mob_type = /mob/living/basic/cortical_borer/empowered + host_egg = /obj/item/borer_egg/empowered + +/obj/item/organ/internal/empowered_borer_egg + name = "strange egg" + desc = "All slimy and yuck." + icon_state = "innards" // not like you'll be seeing this anyway + visual = TRUE + zone = BODY_ZONE_CHEST + slot = ORGAN_SLOT_PARASITE_EGG + /// How long it takes to burst from a corpse + var/burst_time = 3 MINUTES + /// What generation the egg will be + var/generation = 0 + +/obj/item/organ/internal/empowered_borer_egg/on_find(mob/living/finder) + ..() + to_chat(finder, span_warning("You found an unknown egg in [owner]'s [zone]!")) + +/obj/item/organ/internal/empowered_borer_egg/Initialize(mapload) + . = ..() + if(iscarbon(loc)) + Insert(loc) + +/obj/item/organ/internal/empowered_borer_egg/Insert(mob/living/carbon/M, special = FALSE, drop_if_replaced = TRUE) + ..() + addtimer(CALLBACK(src, PROC_REF(try_burst)), burst_time) + +/obj/item/organ/internal/empowered_borer_egg/Remove(mob/living/carbon/M, special = FALSE) + . = ..() + visible_message(span_warning("As [src] is cut out of [M], it quickly vibrates and shatters, leaving nothing but some goop!")) + new /obj/effect/decal/cleanable/food/egg_smudge(drop_location()) + qdel(src) + +/obj/item/organ/internal/empowered_borer_egg/proc/try_burst() + if(QDELETED(owner) || owner.stat != DEAD) + qdel(src) + return + var/list/candidates = SSpolling.poll_ghost_candidates( + role = ROLE_CORTICAL_BORER, + poll_time = 10 SECONDS, + ignore_category = POLL_IGNORE_CORTICAL_BORER, + pic_source = /mob/living/basic/cortical_borer/empowered, + ) + if(!length(candidates)) + var/obj/effect/mob_spawn/ghost_role/borer_egg/empowered/borer_egg = new(owner.drop_location()) + borer_egg.generation = generation + var/obj/item/bodypart/chest/chest = owner.get_bodypart(BODY_ZONE_CHEST) + chest.dismember() + owner.visible_message(span_danger("An egg explodes out of [owner]'s chest, sending gore flying everywhere!"), span_danger("An egg explodes out of your chest, giblets flying everywhere!")) + return + var/mob/dead/observer/new_borer = pick(candidates) + var/mob/living/basic/cortical_borer/empowered/spawned_cb = new(owner.drop_location()) + var/obj/item/bodypart/chest/chest = owner.get_bodypart(BODY_ZONE_CHEST) + chest.dismember() + owner.visible_message(span_danger("[spawned_cb] explodes out of [owner]'s chest, sending gore flying everywhere!"), span_danger("[spawned_cb] explodes out of your chest, giblets flying everywhere!")) + spawned_cb.generation = generation + spawned_cb.ckey = new_borer.ckey + spawned_cb.mind.add_antag_datum(/datum/antagonist/cortical_borer/hivemind) diff --git a/monkestation/code/modules/antagonists/borers/code/items/imprisonment_cage.dm b/monkestation/code/modules/antagonists/borers/code/items/imprisonment_cage.dm new file mode 100644 index 000000000000..b1711f8efded --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/items/imprisonment_cage.dm @@ -0,0 +1,103 @@ +/obj/item/cortical_cage + name = "cortical borer cage" + desc = "A harmless cage that is intended to capture cortical borers." + icon = 'monkestation/code/modules/antagonists/borers/icons/items.dmi' + icon_state = "cage" + + ///If true, the trap is "open" and can trigger. + var/opened = FALSE + ///The radio that is inserted into the trap. + var/obj/item/radio/internal_radio + ///The borer that is inside the trap + var/mob/living/basic/cortical_borer/trapped_borer + +/obj/item/cortical_cage/Initialize(mapload) + . = ..() + update_appearance() + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(spring_trap), + ) + AddElement(/datum/element/connect_loc, loc_connections) + +/obj/item/cortical_cage/update_overlays() + . = ..() + if(trapped_borer) + . += "borer" + if(internal_radio) + . += "radio" + if(opened) + . += "doors_open" + else + . += "doors_closed" + +/obj/item/cortical_cage/attack_self(mob/user, modifiers) + opened = !opened + if(opened) + user.visible_message("[user] opens [src].", "You open [src].", "You hear a metallic thunk.") + else + user.visible_message("[user] closes [src].", "You close [src].", "You hear a metallic thunk.") + playsound(src, 'sound/machines/boltsup.ogg', 30, TRUE) + update_appearance() + +/obj/item/cortical_cage/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/radio)) + internal_radio = attacking_item + internal_radio.forceMove(src) + visible_message("[internal_radio] attaches to [src] with a click.", "You attach [internal_radio] to the [src].", "You hear a clicking sound.") + update_appearance() + return + return ..() + +/obj/item/cortical_cage/crowbar_act(mob/living/user, obj/item/tool) + . = ..() + if(internal_radio) + internal_radio.forceMove(drop_location()) + user.visible_message("[internal_radio] pops off [src].", "You pop off [internal_radio] from [src].", "You hear a clicking sound then a loud metallic thunk.") + internal_radio = null + update_appearance() + return + +/obj/item/cortical_cage/proc/spring_trap(datum/source, atom/movable/AM) + SIGNAL_HANDLER + //it will only trigger on a cortical borer, and it has to be opened + if(!iscorticalborer(AM) || !opened) + return + trapped_borer = AM + trapped_borer.visible_message("[trapped_borer] gets sucked into [src]!", "You get sucked into [src]!", "You hear a vacuuming sound.") + trapped_borer.forceMove(src) + opened = FALSE + if(internal_radio) + var/area/src_area = get_area(src) + internal_radio.talk_into(src, "A cortical borer has been trapped in [src_area].", RADIO_CHANNEL_COMMON) + playsound(src, 'sound/machines/boltsup.ogg', 30, TRUE) + update_appearance() + +/obj/item/cortical_cage/relaymove(mob/living/user, direction) + if(!iscorticalborer(user)) + user.forceMove(drop_location()) + update_appearance() + return + if(opened) + loc.visible_message(span_notice("[user] climbs out of [src]!"), \ + span_warning("[user] jumps out of [src]!")) + opened = FALSE + trapped_borer.forceMove(drop_location()) + trapped_borer = null + update_appearance() + return + else if(user.client) + container_resist_act(user) + +/obj/item/cortical_cage/container_resist_act(mob/living/user) + user.changeNext_move(CLICK_CD_BREAKOUT) + COOLDOWN_START(user, last_special, CLICK_CD_BREAKOUT) + to_chat(user, span_notice("You begin squeezing through the bars in an attempt to escape! (This will take time.)")) + to_chat(loc, span_warning("You see [user] begin trying to squeeze through the bars!")) + if(!do_after(user, rand(30 SECONDS, 40 SECONDS), target = user) || opened || !(user in contents)) + return + loc.visible_message(span_warning("[user] squeezes through [src]'s handles!"), ignored_mobs = user) + to_chat(user, span_boldannounce("Bingo, you squeeze through!")) + opened = FALSE + trapped_borer.forceMove(drop_location()) + trapped_borer = null + update_appearance() diff --git a/monkestation/code/modules/antagonists/borers/code/mobs/cortical_borer.dm b/monkestation/code/modules/antagonists/borers/code/mobs/cortical_borer.dm new file mode 100644 index 000000000000..b0e7953cbedc --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/mobs/cortical_borer.dm @@ -0,0 +1,534 @@ +GLOBAL_VAR_INIT(objective_egg_borer_number, 2) +GLOBAL_VAR_INIT(objective_egg_egg_number, 5) +GLOBAL_VAR_INIT(objective_willing_hosts, 2) + +GLOBAL_VAR_INIT(successful_egg_number, 0) +GLOBAL_LIST_EMPTY(willing_hosts) + +GLOBAL_LIST_EMPTY(cortical_borers) + +GLOBAL_LIST_INIT(borer_first_name, world.file2list("monkestation/code/modules/antagonists/borers/code/first_borer_names.txt")) +GLOBAL_LIST_INIT(borer_second_name, world.file2list("monkestation/code/modules/antagonists/borers/code/second_borer_names.txt")) + +/// This divisor controls how fast body temperature changes to match the environment +#define BODYTEMP_DIVISOR 16 + +//we need a way of buffing leg speed +/datum/movespeed_modifier/focus_speed + multiplicative_slowdown = -0.4 + +/datum/movespeed_modifier/borer_speed + multiplicative_slowdown = -0.5 + +/datum/actionspeed_modifier/focus_speed + multiplicative_slowdown = -0.3 + id = ACTIONSPEED_ID_BORER + +//so that we know if a mob has a borer (only humans should have one, but in case) +/mob/proc/has_borer() + for(var/check_content in contents) + if(iscorticalborer(check_content)) + return check_content + return FALSE + +//this allows borers to slide under/through a door +/obj/machinery/door/Bumped(atom/movable/movable_atom) + if(iscorticalborer(movable_atom) && density) + if(!do_after(movable_atom, 5 SECONDS, src)) + return ..() + movable_atom.forceMove(drop_location()) + to_chat(movable_atom, span_notice("You squeeze through [src].")) + return + return ..() + +//so if a person is debrained, the borer is removed +/obj/item/organ/internal/brain/Remove(mob/living/carbon/target, special = 0, no_id_transfer = FALSE) + . = ..() + var/mob/living/basic/cortical_borer/cb_inside = target.has_borer() + if(cb_inside) + cb_inside.leave_host() + +//borers also create an organ, so you dont need to debrain someone +/obj/item/organ/internal/borer_body + name = "engorged cortical borer" + desc = "the body of a cortical borer, full of human viscera, blood, and more." + zone = BODY_ZONE_HEAD + /// Ref to the borer who this organ belongs to + var/mob/living/basic/cortical_borer/borer + +/obj/item/organ/internal/borer_body/Destroy() + borer = null + return ..() + +/obj/item/organ/internal/borer_body/Insert(mob/living/carbon/carbon_target, special, drop_if_replaced) + . = ..() + for(var/datum/borer_focus/body_focus as anything in borer.body_focuses) + body_focus.on_add() + carbon_target.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + +//on removal, force the borer out +/obj/item/organ/internal/borer_body/Remove(mob/living/carbon/carbon_target, special) + . = ..() + var/mob/living/basic/cortical_borer/cb_inside = carbon_target.has_borer() + for(var/datum/borer_focus/body_focus as anything in cb_inside.body_focuses) + body_focus.on_remove() + if(cb_inside) + cb_inside.leave_host() + carbon_target.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + qdel(src) + +/obj/item/reagent_containers/borer + volume = 100 + +/mob/living/basic/cortical_borer + name = "cortical borer" + desc = "A slimy creature that is known to go into the ear canal of unsuspecting victims." + icon = 'monkestation/code/modules/antagonists/borers/icons/animal.dmi' + icon_state = "brainslug" + icon_living = "brainslug" + icon_dead = "brainslug_dead" + maxHealth = 25 + health = 25 + // They need to be able to pass tables and mobs + pass_flags = PASSTABLE | PASSMOB + density = FALSE + // They are below mobs, or below tables + layer = BELOW_MOB_LAYER + // Corticals are tiny + mob_size = MOB_SIZE_TINY + mob_biotypes = MOB_ORGANIC|MOB_BUG + // Because they are small, why can't they be held? + can_be_held = TRUE + /// What chemicals borers know, starting with none + var/list/known_chemicals = list() + /// What chemicals the borer can learn + var/list/potential_chemicals = list( + /datum/reagent/drug/methamphetamine/borer_version, + + /datum/reagent/impurity/libitoil, + + /datum/reagent/lithium, + + /datum/reagent/medicine/antipathogenic/spaceacillin, + + /datum/reagent/medicine/c2/convermol, + /datum/reagent/medicine/c2/lenturi, + /datum/reagent/medicine/c2/libital, + /datum/reagent/medicine/c2/multiver, + /datum/reagent/medicine/c2/seiver, + + /datum/reagent/medicine/diphenhydramine, + /datum/reagent/medicine/epinephrine, + /datum/reagent/medicine/haloperidol, + /datum/reagent/medicine/inacusiate, + /datum/reagent/medicine/mannitol, + /datum/reagent/medicine/morphine, + /datum/reagent/medicine/mutadone, + /datum/reagent/medicine/oculine, + /datum/reagent/medicine/potass_iodide, + /datum/reagent/medicine/salglu_solution, + + /datum/reagent/toxin/formaldehyde, + /datum/reagent/toxin/heparin, + /datum/reagent/toxin/mindbreaker, + ) + /// Blacklisted chemicals - separate from chemicals that cannot be synthesized, borers specifically cannot learn these + var/list/blacklisted_chemicals = list() + + + /// How old the borer is, starting from zero. Goes up only when inside a host + var/maturity_age = 0 + /// Just a little "timer" to compare to world.time + var/timed_maturity = 0 + + /// How many times you've levelled up over all + var/level = 0 + + /// The amount of "evolution" points a borer has for chemicals. Start with one + var/chemical_evolution = 1 + /// The amount of "evolution" points a borer has for stats + var/stat_evolution = 0 + + /// How many chemical points the borer can have. Can be upgraded + var/max_chemical_storage = 50 + /// How many chemical points the borer has + var/chemical_storage = 50 + /// How fast chemicals are gained. Goes up only when inside a host + var/chemical_regen = 1 + + /// How much health you gain per level + var/health_per_level = 2.5 + /// How much health regen you gain per level + var/health_regen_per_level = 0.02 + + /// How much more chemical storage you gain per level + var/chem_storage_per_level = 20 + /// Chemical regen you gain per level + var/chem_regen_per_level = 1 + + /// The list of actions that the borer has + var/list/datum/action/cooldown/borer/known_abilities = list( + /datum/action/cooldown/borer/toggle_hiding, + /datum/action/cooldown/borer/choosing_host, + /datum/action/cooldown/borer/evolution_tree, + /datum/action/cooldown/borer/inject_chemical, + /datum/action/cooldown/borer/upgrade_chemical, + /datum/action/cooldown/borer/learn_focus, + /datum/action/cooldown/borer/upgrade_stat, + /datum/action/cooldown/borer/force_speak, + /datum/action/cooldown/borer/fear_human, + /datum/action/cooldown/borer/check_blood, + ) + + /// The host + var/mob/living/carbon/human/human_host + /// What the host gains or loses with the borer + var/list/hosts_abilities = list() + + /// Multiplies the current health up to the max health + var/health_regen = 1.02 + /// Holds the chems right before injection + var/obj/item/reagent_containers/reagent_holder + /// Lust a flavor kind of thing + var/generation = 0 + /// List of focus datums + var/list/possible_focuses = list() + /// What focuses the borer has unlocked + var/list/body_focuses = list() + /// How many children the borer has produced + var/children_produced = 0 + /// How many blood chems have been learned through the blood + var/blood_chems_learned = 0 + /// We dont want to spam the chat + var/deathgasp_once = FALSE + // The limit to the chemical and stat evolution + var/limited_borer = 10 + /// Borers can only enter biologicals if true + var/organic_restricted = TRUE + /// Borers are unable to enter changelings if true + var/changeling_restricted = TRUE + /// Assoc list of chemical injection rates that the borer can have + var/static/list/injection_rates = list( + 5, + 10, + 25, + 50, + ) + /// Which injection rates the borer has unlocked + var/list/injection_rates_unlocked = list( + 5, + ) + /// What is the current injection rate of the borer + var/injection_rate_current = 5 + /// Cooldown between injecting chemicals + COOLDOWN_DECLARE(injection_cooldown) + /// Evolutions we've already learned + var/list/past_evolutions = list() + /// Bitflag of upgrades and effects the borer has + var/upgrade_flags = 0 + /// If the borer has evolved with a genome that locks out others of the same & higher tier + var/genome_locked = FALSE + /// Multiplier for a borer's negative effects to their host + var/host_harm_multiplier = 1 + + /// Controls if the borer can reproduce or not, TRUE means it wont be able to spawn eggs + var/neutered = FALSE + /// Used to give the borer the antagonist datum + var/antagonist_datum = /datum/antagonist/cortical_borer/hivemind + + /// Skips unique borer status tab text, used for unique borer subtypes with their own status tabs + var/skip_status_tab = FALSE + +/mob/living/basic/cortical_borer/can_track(mob/living/user) + return FALSE // The validhunt box machines are onto us, we cannot let them track us + +/mob/living/basic/cortical_borer/Initialize(mapload) + . = ..() + AddComponent( \ + /datum/component/squashable, \ + squash_chance = 25, \ + squash_damage = 25, \ + squash_flags = SQUASHED_DONT_SQUASH_IN_CONTENTS, \ + ) + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) //they need to be able to move around + + var/matrix/borer_matrix = matrix(transform) + borer_matrix.Scale(0.5, 0.5) + transform = borer_matrix + + create_name() + + GLOB.cortical_borers += src + reagent_holder = new /obj/item/reagent_containers/borer(src) + + for(var/action_type in known_abilities) + var/datum/action/attack_action = new action_type(src) + attack_action.Grant(src) + + if(mind) + if(!mind.has_antag_datum(antagonist_datum)) + mind.add_antag_datum(antagonist_datum) + + for(var/focus_path in subtypesof(/datum/borer_focus)) + possible_focuses += new focus_path + + do_evolution(/datum/borer_evolution/base) + INVOKE_ASYNC(src, PROC_REF(resolve_misc_issues)) // if things can fail, they will + +/mob/living/basic/cortical_borer/Destroy() + human_host = null + GLOB.cortical_borers -= src + QDEL_NULL(reagent_holder) + return ..() + +/mob/living/basic/cortical_borer/death(gibbed) + if(inside_human()) + var/turf/human_turf = get_turf(human_host) + forceMove(human_turf) + human_host = null + GLOB.cortical_borers -= src + if(!deathgasp_once) + deathgasp_once = TRUE + for(var/borers in GLOB.cortical_borers) + to_chat(borers, span_boldwarning("[src] has left the hivemind forcibly!")) + if(gibbed) + QDEL_NULL(reagent_holder) + return ..() + +//so we can add some stuff to status, making it easier to read... maybe some hud some day +/mob/living/basic/cortical_borer/get_status_tab_items() + . = ..() + if(skip_status_tab) + return + . += "Chemical Storage: [chemical_storage]/[max_chemical_storage]" + . += "Chemical Evolution Points: [chemical_evolution]" + . += "Stat Evolution Points: [stat_evolution]" + . += "" + if(host_sugar()) + . += "Sugar detected! Unable to generate resources!" + . += "" + . += "OBJECTIVES:" + . += "1) [GLOB.objective_egg_borer_number] borers producing [GLOB.objective_egg_egg_number] eggs: [GLOB.successful_egg_number]/[GLOB.objective_egg_borer_number]" + . += "2) [GLOB.objective_willing_hosts] willing hosts: [length(GLOB.willing_hosts)]/[GLOB.objective_willing_hosts]" + . += "3) [GLOB.objective_blood_borer] borers learning [GLOB.objective_blood_chem] chemicals from the blood: [GLOB.successful_blood_chem]/[GLOB.objective_blood_borer]" + +/mob/living/basic/cortical_borer/Life(seconds_per_tick, times_fired) + . = ..() + //can only do stuff when we are inside a LIVING human + if(!inside_human() || human_host?.stat == DEAD) + return + + //there needs to be a negative to having a borer + if(prob(5 * host_harm_multiplier * ((upgrade_flags & BORER_STEALTH_MODE) ? 0.1 : 1)) && human_host.getToxLoss() <= (80 * host_harm_multiplier)) + human_host.adjustToxLoss(5 * host_harm_multiplier, TRUE, TRUE) + + human_host.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + + //cant do anything if the host has sugar + if(host_sugar()) + if(!has_status_effect(/datum/status_effect/borer_sugar)) + apply_status_effect(/datum/status_effect/borer_sugar) + else + if(has_status_effect(/datum/status_effect/borer_sugar)) + remove_status_effect(/datum/status_effect/borer_sugar) + + //this is regenerating chemical_storage + if(chemical_storage < max_chemical_storage) + if(!(upgrade_flags & BORER_STEALTH_MODE)) + chemical_storage = min(chemical_storage + chemical_regen, max_chemical_storage) + + //this is regenerating health + if(health < maxHealth) + if(!(upgrade_flags & BORER_STEALTH_MODE)) + health = min(health * health_regen, maxHealth) + + //this is so they can evolve + if(timed_maturity < world.time) + mature() + +//if it doesnt have a ckey, let ghosts have it +/mob/living/basic/cortical_borer/attack_ghost(mob/dead/observer/user) + . = ..() + if(ckey || key) + return + if(stat == DEAD) + return + var/choice = tgui_input_list(usr, "Do you want to control [src]?", "Confirmation", list("Yes", "No")) + if(choice != "Yes") + return + if(ckey || key) + return + to_chat(user, span_warning("As a borer, you have the option to be friendly or not. Note that how you act will determine how a host responds!")) + to_chat(user, span_warning("You are a cortical borer! You can fear someone to make them stop moving, but make sure to inhabit them! You only grow/heal/talk when inside a host!")) + ckey = user.ckey + if(mind) + mind.add_antag_datum(antagonist_datum) + +/mob/living/basic/cortical_borer/proc/create_name() + // So their gen and a random. ex 1-288 is first gen named 288, 4-483 is fourth gen named 483 + // Additionally we add in a random title, + // mainly so people can ahelp borers quicker and admins dont have to look through the logs of the 5 borers that were inside you + name = "[initial(name)] ([pick(borer_first_names)]: [pick(borer_second_names)]) ([generation]-[rand(100,999)])" + + if(istype(/mob/living/basic/cortical_borer/empowered, src)) // lets also distinguish empowered borers from normal ones + name = "larger [name]" + + if(generation == 0) //The first ever borer gets a special name + name = "The hivequeen [initial(name)]" + +// if things can go wrong, they will. So this proc is an emergency measure meant to resolve them +/mob/living/basic/cortical_borer/proc/resolve_misc_issues() + sleep(5 SECONDS) // give everything some time to resolve itself, if it fails then we come in + if(name == initial(name)) + message_admins("[ADMIN_LOOKUPFLW(src)] had its name initialization fail, this should never happen! Automatically gave a backup name.") + create_name() + + if(mind) // can actually happen if admins turn someone into a borer in a weird enough way + if(!mind.has_antag_datum(antagonist_datum)) + message_admins("[ADMIN_LOOKUPFLW(src)] had its antag datum initialization fail, this should never happen! Automatically gave the mob antag datum [antagonist_datum]") + mind.add_antag_datum(antagonist_datum) + + else // apparently can happen with the neutered borer spawner, not a damn clue what causes it + message_admins("[ADMIN_LOOKUPFLW(src)] spawned despite having no ghost, automatically informed ghosts!") + notify_ghosts( + "[src] needs to obtain a ghost, click on it to submit yourself!", + source = src, + action = NOTIFY_ORBIT, + header = "The code did not give it one" + ) + +//check if we are inside a human +/mob/living/basic/cortical_borer/proc/inside_human() + if(!ishuman(loc)) + return FALSE + return TRUE + +//check if the host has sugar +/mob/living/basic/cortical_borer/proc/host_sugar() + if(upgrade_flags & BORER_SUGAR_IMMUNE) + return FALSE + if(human_host?.reagents?.has_reagent(/datum/reagent/consumable/sugar)) + return TRUE + return FALSE + +/// Base mob environment handler for body temperature, overridden to take into consideration being inside a host +/mob/living/basic/cortical_borer/handle_environment(datum/gas_mixture/environment, seconds_per_tick, times_fired) + var/loc_temp + if(human_host) + loc_temp = human_host.coretemperature // set the local temp to that of the host's core temp + else + loc_temp = get_temperature(environment) + var/temp_delta = loc_temp - bodytemperature + + if(!human_host && ismovable(loc)) + var/atom/movable/occupied_space = loc + temp_delta *= (1 - occupied_space.contents_thermal_insulation) + + if(temp_delta < 0) // it is cold here + if(!on_fire) // do not reduce body temp when on fire + adjust_bodytemperature(max(max(temp_delta / BODYTEMP_DIVISOR, BODYTEMP_COOLING_MAX) * seconds_per_tick, temp_delta)) + else // this is a hot place + adjust_bodytemperature(min(min(temp_delta / BODYTEMP_DIVISOR, BODYTEMP_HEATING_MAX) * seconds_per_tick, temp_delta)) + +//leave the host, forced or not +/mob/living/basic/cortical_borer/proc/leave_host() + if(!human_host) + return + var/obj/item/organ/internal/borer_body/borer_organ = locate() in human_host.organs + if(borer_organ) + borer_organ.Remove(human_host) + var/turf/human_turf = get_turf(human_host) + forceMove(human_turf.drop_location()) + human_host = null + +//borers shouldnt be able to whisper... +/mob/living/basic/cortical_borer/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language, ignore_spam = FALSE, forced, filterproof) + to_chat(src, span_warning("You are not able to whisper!")) + return FALSE + +//previously had borers unable to emote... but that means less RP, and we want that + +//borers should not be talking without a host at least +/mob/living/basic/cortical_borer/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) + if(!inside_human()) + to_chat(src, span_warning("You are not able to speak without a host!")) + return + if(host_sugar()) + message = scramble_message_replace_chars(message, 10) + message = sanitize(message) + var/list/split_message = splittext(message, "") + + //this is so they can talk in hivemind + if(split_message[1] == ";") + message = copytext(message, 2) + for(var/borer in GLOB.cortical_borers) + to_chat(borer, span_purple("Cortical Hivemind: [src] sings, \"[message]\"")) + for(var/mob/dead_mob in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(dead_mob, src) + to_chat(dead_mob, span_purple("[link] Cortical Hivemind: [src] sings, \"[message]\"")) + var/logging_textone = "[key_name(src)] spoke into the hivemind: [message]" + log_say(logging_textone) + return + + //this is when they speak normally + to_chat(human_host, span_purple("Cortical Link: [src] sings, \"[message]\"")) + var/logging_texttwo = "[key_name(src)] spoke to [key_name(human_host)]: [message]" + log_say(logging_texttwo) + to_chat(src, span_purple("Cortical Link: [src] sings, \"[message]\"")) + for(var/mob/dead_mob in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(dead_mob, src) + to_chat(dead_mob, span_purple("[link] Cortical Hivemind: [src] sings to [human_host], \"[message]\"")) + +//borers should not be able to pull anything +/mob/living/basic/cortical_borer/start_pulling(atom/movable/AM, state, force, supress_message) + to_chat(src, span_warning("You cannot pull things!")) + return + +/// Called on Life() for the borer to age a bit +/mob/living/basic/cortical_borer/proc/mature() + . = TRUE + if(upgrade_flags & BORER_STEALTH_MODE) + return FALSE + timed_maturity = world.time + 0.5 SECONDS + maturity_age++ + + /** + * The point values are double what they seem + * So in the beginning you start out with the following generation: + * Evolution point per 40 seconds + * Chemical point per 20 seconds + */ + + //20:40, 15:30, 10:20, 5:10 + var/maturity_threshold = 2 SECONDS + if(GLOB.successful_egg_number >= GLOB.objective_egg_borer_number) + maturity_threshold -= 0.2 SECONDS + if(length(GLOB.willing_hosts) >= GLOB.objective_willing_hosts) + maturity_threshold -= 1 SECOND + if(GLOB.successful_blood_chem >= GLOB.objective_blood_borer) + maturity_threshold -= 0.3 SECONDS + + if(maturity_age == maturity_threshold) + if(chemical_evolution < limited_borer) //you can only have a default of 10 at a time + chemical_evolution++ + to_chat(src, span_notice("You gain a chemical evolution point. Spend it to learn a new chemical!")) + else + to_chat(src, span_warning("You were unable to gain a chemical evolution point due to having the max!")) + if(maturity_age >= (maturity_threshold * 2)) + if(stat_evolution < limited_borer) + stat_evolution++ + to_chat(src, span_notice("You gain a stat evolution point. Spend it to become stronger!")) + else + to_chat(src, span_warning("You were unable to gain a stat evolution point due to having the max!")) + maturity_age = 0 + +/// Use to recalculate a borer's health and chemical stats when something retroactively affects them +/mob/living/basic/cortical_borer/proc/recalculate_stats() + var/old_health = health + maxHealth = initial(maxHealth) + (level * health_per_level) + health_regen = initial(health_regen) + (level * health_regen_per_level) + max_chemical_storage = initial(max_chemical_storage) + (level * chem_storage_per_level) + chemical_regen = initial(chemical_regen) + (level * chem_regen_per_level) + health = clamp(old_health, 1, maxHealth) + +#undef BODYTEMP_DIVISOR diff --git a/monkestation/code/modules/antagonists/borers/code/mobs/empowered_borer.dm b/monkestation/code/modules/antagonists/borers/code/mobs/empowered_borer.dm new file mode 100644 index 000000000000..092dd92a621b --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/mobs/empowered_borer.dm @@ -0,0 +1,17 @@ +/** + * They can only spawn from a dead body that had an egg implanted into it + * Starts SIGNIFICANTLY stronger than any other option you can get + */ +/mob/living/basic/cortical_borer/empowered + maxHealth = 150 + health = 150 + health_per_level = 15 + health_regen_per_level = 0.04 + + stat_evolution = 8 + chemical_evolution = 8 + + max_chemical_storage = 250 + chemical_storage = 250 + chem_regen_per_level = 1.5 + chem_storage_per_level = 25 diff --git a/monkestation/code/modules/antagonists/borers/code/mobs/name_lists.dm b/monkestation/code/modules/antagonists/borers/code/mobs/name_lists.dm new file mode 100644 index 000000000000..e902a07c552f --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/mobs/name_lists.dm @@ -0,0 +1,56 @@ +/mob/living/basic/cortical_borer + var/list/borer_first_names = list( + "Whispering Willy", + "Creepy Carl", + "Slinking Sam", + "Tunneling Tim", + "Sly Sid", + "Murmuring Maggie", + "Hushed Hank", + "Drifter Dave", + "Murky Molly", + "Mysterious Mike", + "Hush-Hush Harry", + "Slithering Steve", + "Eerie Emily", + "Silent Stan", + "Covert Connie", + "Stealthy Sarah", + "Wriggling Walter", + "Sneaky Sue", + "Serpentine Sally", + "Quiet Quincy", + "Shhh-Shhh Sharon", + "Rustling Rupert", + "Lurking Larry", + ) + var/list/borer_second_names = list( + "The Ear Intruder", + "Master of the Ear Canal", + "Whisperer of Secrets", + "Ear Explorer Extraordinaire", + "Whisperer in the Dark", + "Eardrum Trespasser", + "Ear Conqueror", + "The Inner Ear Invader", + "Whispering Wanderer of Ears", + "Mistress of Ear Infiltration", + "The Silent Traveler", + "Ear Canal Navigator", + "Master of Ear Intrusion", + "Enchanter of Auditory Passages", + "Ear Passage Pioneer", + "Whisperer of Inner Depths", + "Eardrum Sleuth", + "Ear Whisperer", + "Maestro of the Ear", + "Stealthy Ear Invader", + "Master of Ear Infiltration", + "Whispering Intruder", + "Siren of the Ear", + "Eardrum Explorer", + "Secretive Ear Seeker", + "Eardrum Navigator", + "Slinker of Ears", + "Laird of the Ear Canal", + ) diff --git a/monkestation/code/modules/antagonists/borers/code/mobs/neutered_borer.dm b/monkestation/code/modules/antagonists/borers/code/mobs/neutered_borer.dm new file mode 100644 index 000000000000..4a96ece1ceca --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/mobs/neutered_borer.dm @@ -0,0 +1,18 @@ +/** + * A version of the standard borer that can't reproduce + */ + +/mob/living/basic/cortical_borer/neutered + antagonist_datum = /datum/antagonist/cortical_borer + neutered = TRUE + skip_status_tab = TRUE + generation = 1 + +/mob/living/basic/cortical_borer/neutered/get_status_tab_items() + . = ..() + . += "Chemical Storage: [chemical_storage]/[max_chemical_storage]" + . += "Chemical Evolution Points: [chemical_evolution]" + . += "Stat Evolution Points: [stat_evolution]" + . += "" + if(host_sugar()) + . += "Sugar detected! Unable to generate resources!" diff --git a/monkestation/code/modules/antagonists/borers/code/status_effects.dm b/monkestation/code/modules/antagonists/borers/code/status_effects.dm new file mode 100644 index 000000000000..edfca2eff0ec --- /dev/null +++ b/monkestation/code/modules/antagonists/borers/code/status_effects.dm @@ -0,0 +1,11 @@ +/datum/status_effect/borer_sugar + id = "borer_sugar" + tick_interval = -1 + status_type = STATUS_EFFECT_UNIQUE + alert_type = /atom/movable/screen/alert/status_effect/borer_sugar + +/atom/movable/screen/alert/status_effect/borer_sugar + name = "Sugar Dampening" + desc = "Your powers are diminished while sugar is in you or your host!" + icon = 'monkestation/code/modules/antagonists/borers/icons/actions.dmi' + icon_state = "borer_sugar" diff --git a/monkestation/code/modules/antagonists/borers/icons/actions.dmi b/monkestation/code/modules/antagonists/borers/icons/actions.dmi new file mode 100644 index 0000000000000000000000000000000000000000..caf6dba6136c0764ae8099d8f79bf180ee007e8a GIT binary patch literal 6184 zcmaKQWmHsO^!6PZ1{^{{q(MNsyF?L?25F>}QW}&Rh5)H2t_TCY?+NxwE^dtZPkg2OZ)5qFx*auETfPF{5 zwta{->;5lIyr0>5z4ml+_jYn~1pwd7n31a34k1$bva}xwc!I=(c7m^v?H|Yzlpir` zl1Kbv)TkLlMymOD-gf(VNipC;)~e}Y?WDHAS8zTNy>@u+ecr~MCm*j;I@n`cTf_CW zRE8^$i~eQZkM#tRDe99>YhJ*D$fe&ToJu8#2Zw=Z*T!2QoC+oqwv35cVDNQ(;%q@F z(kWx^Lob}O#xb9qJGPWpN6e}^GtZr_A1wCsxp=W&PF*yZ|AxyRy0K>8*GHUdKE`la zKw&&)@a<|Pw;=k`aswSuDWB=aj}7b=cbIl^++-|70RU)j)t^0i>6?kl3h<_y%tE$_ zPV{|o?z_P6iZf{EwY8-*mrIm*6~Q@O;Nq{J*_F?+Zx4Bu(f(a4bu|z5rDnbW^+987 zEE!jQDt=8#BZF@$9HG7y3{M^vllVdJUvzY$aLb}8Du*h?R)Y?PGoLK zS|)L0aBi_h)C#lzZiDO38uEpTOfpWqh@S`#egKdAcBdjxdsVw#P5w z_tn4S^ed4*XXiS5b-}bRbt@aR=#Eq;rndg%sm?-k^goM7t`ccFDiK_|M{TGU^VE{>8G!mj`m z-MMVnoDB!JbIsN>y1y`NeYPx__Dm^NIa`x2D=%zzaHyW`<1;!#c&N-f@K~LvBq2~Y z!8O&~6hsihlst{j*t`cmXQuaF2ek(e@i%2S@0vBrKBC@?DzB{kDJCenDe(!thzB@- z89jL5@P-{X>8K>O1Xo*X;m8knb5kQ!b>;&DwJZCk?KJ}TT=%*r!W14_ceYqvTEKB0 zp~w*M0Fa6Wi%Gih*G~_o0!kj}9UDOE{ORmWPqD<*TvTPr@)Mx_&19c?)S=pJY~~NP z_bwk0{oF~9HNYfa93x4pW^eJlhX-V6`Ey2-mcNNP=ph3Ci$uei6R z3Q!#B_&0c=2Ul{-o_;KE_Wo?mIvZyW2M(_lq1SZ*w@%%(pl_x9z7wk+W%$6a76>4K z^RrpK?tTS>@NW}C9D@V*Q8o_CipokyQ&ZFB&Ew+~D_!n2{1;(=3SSoeVp%enbkaS*w?bD=v?yAjgIdcB&8ke{aVvCB=}-AZk3sAeJx5{Qg4&cQ4P z-FDobo*Lp4&ITa$ABTjwDwaPX()0Hko3M>T;sbPaLS>Bftt7zD#`T5sCP9WamtqGz zYBDVV?zTz|y*`3eZp@>$O6Q~InpE{B$2+=tlv@uyy$s)9UP_BU>W*?9ajfQD_mp_& z@9+Qi&Dx9hgH_Jz>gqXx!cL@SNi(Sa&n8%?({wBytkN3k!OX6)4Uh5>=w;7}8XR1n z*eDoZ2jWZa2Gg%)H6K3+vlKjg-NX%jjf-1QCo$`1(KHtxc|=XC@nRyT$r5g>YF0_(npnFBF>>G+ceUornVHs5nBZ#K zk&ZM{er!idk;U-SQ z1c=#pqN|%N96){Ejiea3T?s;ln50NzKEHR|E#*IQ z%WU$8n%aTbbH0kz{@bVmSSp&%#}*b=QNYnJqy6lja9<_E4@P$FLr%3b9o70S3-pbP z^~uB+)RsBM8}6D~I*oy^UE>a@*ct$2pvd}Z+wP$+;pLe)$t7i2$SeM&s%}%#*T??2 zZb(@Y?eRvU@q=gEiJ1|;&%aAXVk&c&TCNe9r zl@`tbS5Emgh2QS>p={;iIPyEmCcur>ycVA>ZqAn`@yiP8ZJCl^f6hWar}fNT`?IFl z!F;_6e^O1!1A;!OdzhXcewpv}WQ7aTZ$ zSsHyPn?un&E~Rg`Jv5S3{!F4Bt;<9Y#D+CcdgjLKgY>!4%5KOXY9!PwD*FFFP0Qnm zKKy>ExV$FpKK0j%O}#FGrsyo0@jC!6Mp!lF$U{u4h_@&pR3`1x?3m_~*d=OcpT zpUSh?(cBC(7CFXV`h5<=@9_oEUsUM|-7RvO>R%epT$PD9SQkbyvtZmNV@sKU^(_O zbhn~El{Rq(#$G{cpA_NLwHMW|vzaPx25mP{MURv_lm@mtUUofFvI$aF<0MeB`21Ci zbAvo&A!;#|y$Tb$%@Na<#lYS#Id%mXR-{JKrhXm%i(q$=jotSv_yt4Ozco`gV?%GV z5zCKeS`@FWK6-2Qc2qA&IAPq>+&+uB>ND-g5s>K1XTYt-*6{LAAr)2dRJV>XwAlnT z#I^ZlTec$oPUKz2qKmnFo2HqHuhpuZB+1Vm|QjGIM7y-EgbbUGC9ooFNpjM>y~oJ< zmU;h~e^8H)=*!--Ma+T~mWuwvr1d{4V*M5C5w3l-+S#q^Ffu4srE}%i1yi|RtC#|2<60>!K~cooTjcG|CW!TokU6mUzVIT>P;+avriX2!D4tQ2%WSC-UWl zV&+A)mEVgK(O};W$IB>xrK$^e0=$+%{t`sRJi091sJ4X+29?@P%3<`|Yr89Tb2Nb+ z3HRTdPw7E#kE)0;3F%`N&!S6^NYpCOOyYZ=L}@wuYXNM0)*+F_dVvb9@{zM*s{dTu!G2Sy4nmkU4UXW<{kHUR8%a3_T1>{ma5(foZV{ zIJ3W=_5lF`Ig3ce=S8M-yb@dL1-DqBwJqPfHFvAF3zObi8~=l!2*-q+@6UR!j2kO9 zWwe!jx4Be@+5O-jc=Hz8^d>D%PpIwoy-saeO%9p1AGdf^z~u)b+cU~CMW>M9#_Y;A z*{!HA<6D~ysaS&OND_e1^)R`YMbOryy$DcxLlH?)aslwP=OrkUcNNeTAyinjgwqbd zs&Xs1KLLY~`yufmCIzqA@P5$#Pfh$F@F~ywRrvpw$9exhwgI!rnK#pseya3YNgvp# zT};$LAziJ!`KP98l0M=Kxxmti>j$Q4yvh@#M#oh0dZ|YJD1Krt_PEwS-FPIxaQByN z0ox+EX+cI>)-hCtX$?VER@MW>{>gQs(^44+;C$={DD&INO-#~ss#*#m2tVW5M z$%KUv;fP@s-yV;HGm}WGR9({ejZK(lnxu?u=Yk~fgUNNV{&exth0y+bK^4=E>C~&{ z&k4gUU3mfN!nRzrpRIBLkmSJgH%s;%s06YfBu`um(6w+RC zIbzFXr87U#@ys?0N9|V1|=A~cU)&O@l8G~Ou|JW-Qb z?X(M#e`_UkOEMYOSw6Q-4hc?&x~!0Cf70WS;OXy zt>jC;j|oXik&$IEc6V!VRJqiUaUOuk2-qDYG>*?tlmFS)kQHxqYI~q5B`-6R0kTg^ z%=h&v1fb4{Cx(`mOp%c<=0?uQ@NjBcYn=CWMk7bffIa8z-@fO7AR@2 zyFt-tv5lBTdEtHGG5Hc^QSOcV6T3Fo`_fKL~%KnuTp+9snNI!4%MX0D?t90_D(a% z)>Q4srHoURGlwP1{e>C*_Q3m2812S3!*;=UB;pwTIOv&>9!>E@=$(R2D|oS!5gD|3 z@Ni!$WZpIB^zDIEu4Pe)NBO^TsW7Jz%e98HSkbpO^f?W9jvv0#ZGlE;pR9l)o{V)m zk%mjhEW7`?A*WnURN`CvbtpgQu{Q54A`D#)lf6geuXI#>=3O#b@oUTpwQY1Sg#E!Ysmc{!%R5f0oX(82GfB_n%Pp-(!3o%rw=eIN#ml zdyjSNlt+{Ek8>n6FmypMkyhgm_(3&YCF*<)*jZMj_7&bq{&{)yXP`W|Rwm8m-LaN=N?kma- z9W`J6uZmY$arTqeTg(0SjIW1J+;3n7c0r;iT0_UNQxKDM>k-0~8B(we=<5KOS3u?-yA-|6u-H3fCU%g$~NKiQqe;X4?4$}ExF}h zIQH)20p39sGzWYNGfh5=U#PIHcLNu-9Zvc97~J}<*kjj}0p~D9tXnlxN9|5?N{6Z% zHlL-RfoDtcg~^P8e8vVMX}qQw0v-dijz}06Irjf&A2QMBOkY|3SoOcIcq7 ztuy)Prydjy>X}@$t$McjIBd(?>*ELH4k-BBWWbwOWd|uw4|saWpQ-L!$foOHwe_7QL+)2RI=5!2Jl#+%aQpwFT~H8}zx zcn3=M4htYgA%_`&L0bnReD^;^s6wHXXuSU>BZ2O#*@PFfmavmgKHTTVnBn$E z_l*}K4T7YD>&9hHFm^<$_sh7et7tlp>`s9tjF5F>9(vcK&)oO)T4#=I59yZusy7AO z_DV_Bn_wK|SVGW8p%l3*WkH-`zo8J5d@r7WfPZ5*Cp=jOjZOygV8BvGaTUFV({_9M zE*Omn>hZT;)##5y%=e7*_2!7zO~U+=ce{9RM@IA|hf8kH#~+U0eUQRUMFCcMHB8C_1}}WZS4`COEzt zSX(yTCZ30IlMXzhDdxo{F-1fVL(|>%BuaUlf z*hrHMqs}A;e=<9j8sfkM=LY0%4srW+17w@xa`4~)+7p$k>Tp`tQ`ObC;#?;VDv zTRq60J&)X~VZ&f}p5U^yesHeun*;u}F!#>!WwgH_+9m{5g}G@N{}p19df+0lOUKf1 z|6I&1>|-f7gRv>iW!csGGok)n?zy5OyTZ|G3GC+7G}nfu>gxRDq`w4+KvI{7l7l#F zR#@e-@6zl{Ich1{-Z#q>vGOvPzaH?~sc70QGCd9k7u|NGkh8U`l`vK*A1!TCH?fLW z6uj2*&4h_KKl_P`Uliiw4aqLTZjA~9-SBS literal 0 HcmV?d00001 diff --git a/monkestation/code/modules/antagonists/borers/icons/animal.dmi b/monkestation/code/modules/antagonists/borers/icons/animal.dmi new file mode 100644 index 0000000000000000000000000000000000000000..68bcff0834a6b87344ea8a661d1484d0505936c4 GIT binary patch literal 2000 zcmaJ?cTm&W7XF0<2q4m>tc#!^gdzwEB1lUF0uf^-A~n*LP=Zl90wRRI^aX@PRxwJ4 zpcFxrfYOx`nof=CMl zhNGPiFO-KPXLExta8z0-*7hF4Xe|U?oiosz>9};M%8q@1{d)`vEPwB->TFK^w%(q93<W9cV5ug>VaWHFvM4KEP$!~ht` z_|N_eps(1;E|9Z5B=hTu0WQm)bL2ntbrwK!dLj9~c)Teu;8za@*uV=3{*(&x>ybCC zZ=2s_{PwiF6`C#I!G*invrj~aX*3An>|gDqI~yL|CG?>vTQV?kx~Xt{VCI~pq!$)Q zyE#=HAZb$@LNHQm;Dv&XE{s(sB`2F7M1f%{l~>Qu&GZcI0kn(#;U5RX&4dtshmmCISz+ z)?utOB@X`d=3I03mkDgPCjUHMJ_zXT#v>UTAd{hlVZm*vFWmy#33e%*HrSay#!2ZlgvLH&*(rI;_z!;S*y z>J5`bH%eKF`)o|~D(-CahIzxsO5f85N)HsH!{u3-a$FNNIAac%iT>&-}lY$w^~5)Dy}6k&X2XGdUp`IXWo(C`>a|PVCt@q5e$youc@k+ zh{?@)!sDzJNHgv!!$UXQZPr|~N(*9q3A%Rh&PiZ+>Y|QNb*~Yc)0MYlva-zJ%0Z z`S0ICex>>zN^a7*ADHm{R=6~o3;yU;#hYB#{to?%FXMgq1NpwtWi$zi3ZsElY^WjM zt`*Wl3Up=Xb5yok=*H0}Q2O!#(yu3kt#OI5d_^b=C+-Tw|6^gy+`Tp8JLfr1y8juS zi-@W%H!g&;CNcc7N5ZIgjp&%LxLivsZ&YHpXqBu86lDx-2^gA`Lv$eA@k1fMq^GZ> z^)!zAa`=~-c@^=h0}}5}7FE7ICal0U?IW4L2mAm%;XA!xYAXbdN=i98zR1>(nAUfS zG^gzWVW-u&B5&xsvcMdGYCkMPC}YFZ6u67Li*oIY?4Lp2j<1bI^AHl~G_LrovaA6D zrwUPv{8t7IOQ^i24j(AxS-Mhss(R!li^d5~M$>h=1knh@OJ5Gjd4U^Mle%%G5Txon zNY2d-*Ps2f$vy1uYENpXj3-I%uq!KUN|b!T#?e~1=h%pt#k=F=lXMCENrkd3qq}u8 z5Bv|-Uf2d4HkQYJ!NQ>Y!}MBa2BYsj8&~#*zHR$>`3>Ric>NS3uzg+V0u82el#_w9 zg62Lm5*aPB@|hD3BOv={i(sa`tLW}cfwJvbn{I(kMWgod66Bqhoppl~KTgq&W1t6^ zq_?b^iS}MQm{9Yh_0{X$JKhEyW;5T_OuxE;uMdjf7VOj@3>LoFdg<1(5nF}S7;kiY z7*RPT_dePlmR^jBsMHWg!RV&HtygsYlz4)vG#R|5H`CE#w5`#YL*DCjeIWHB5$!Z33t z!x;t!YX*jw%{lcz1&k#@e!&b5&u*jvIR#ZA5hX6E#mPmP1tppJc?=8{bArPPib}tK z2`>2f^@*0ZuGYCT=Yuzd8eBAf@JQ#pkLF2+qMqIz7REuwmyNv?<~*8oWKxJiu;yYD z)s-5C#_MhNYlL@p8>?}M>{ZX-_@ z#}JK)$q6DNA}J1RPN&?uRxoKPW-xi3d!f}6$m%jBCV=(GoVfw44cE8~dYT;h8J07% Vh}|#BUi1I} literal 0 HcmV?d00001 diff --git a/monkestation/code/modules/antagonists/borers/icons/items.dmi b/monkestation/code/modules/antagonists/borers/icons/items.dmi new file mode 100644 index 0000000000000000000000000000000000000000..156981031ab7e78d6c47cd0fb5f741eae9b0c455 GIT binary patch literal 1007 zcmVV=-0C=2*%&`i>KoA7b`t~b^q!lsI_8Li$6v;2hnOl>Cn3cUl@$W6th$!i1 z;LYSX)~BXJ)i(5CjX`Eu9JV_$&*0Mti+xFEkBDKWXku*kfK#%Rg&>Mxfd*o&Bx>-%KGm(N8MGZ+J@d1SxlmsU#+G9 z00R$6L_t(|ob8;=YZE~j#@|gt)5zjStCXS$9yAB-#fgGNN}*c6dMJt>1kd>c3SK4W zQZJrDPhR{3s;59O?M1=OA>d^LQlSX(VybQ;h-E#L-K5E8lXYkJ$*%A3l8|KQ-I-^e zcV=FeDCKgQ7#s=o4gBx00AN@EFiG?cj5@lmn|`j(WHO4|_v`={{0{ywZLk1+kTLkf zwB?wBR8=(%!*I2ZSG=^Ts;ackcj6_!5JGl%$Nu*S5LD-_wg(MD=WldfH?15x=>0Ii z9X5V0m-E|IM+${PPkhG|BzyJ;)n@15MwLSX1UdVIh+)lqS?wW+^dcN92L!S3I$;=w zBFhD_azLe02`#9sSg~k2kHwu08FF>h{GD7XqsjkhM{<^8hXX4%Goc@D3qhaitkvC4yqbDY}<%)2`YZ1 zQ&1e}DMtsXzRW^gTsR~^Sk=&=V?+sCfH3#IVa6JD>}qJ3Gm}$Mn*GL=(f7h$h1si= zK6#xT;Fn@4uO*u;@#A~T{%qyR3n>Ri761(Ig21o { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + Your current objectives: + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; + +export const AntagInfoBorer = (props: any, context: any) => { + const [tab, setTab] = useLocalState(context, 'tab', 1); + return ( + + + + setTab(1)}> + Introduction + + setTab(2)}> + Ability explanations + + setTab(3)}> + Borer side-effects + + setTab(4)}> + Basic chemical information + + + {tab === 1 && } + {tab === 2 && } + {tab === 3 && } + {tab === 4 && } + + + ); +}; + +const MainPage = () => { + return ( + + +
+ + + You are a Cortical Borer, a creature that crawls into peoples + ear's to then settle on the brain + + + + + +
+
+ +
+ + + Host and you +
+ + You depend on a host for survival and reproduction, you slowly + regenerate your health whilst inside of a host but whilst + outside of one you can be squished by anyone stepping onto you, + killing you. + +
+
+ + When speaking, you will directly communicate to your host, by + adding " ; " to the start of your message you will + instead speak to the hivemind of all the borers + +
+
+ + Creating resources and their uses + +
+
+ + While inside of a host you will slowly generate internal + chemicals, evolution points and chemical points. + +
+
+ + Internal chemical points + are used for using most of the abilities, their main use is in + injecting chemicals into your host using the chemical injector + +
+
+ + Evolution points + are mostly used in the evolution tree and choosing your focus, + both of those being essential to surviving and completing your + objectives + +
+
+ + Chemical evolution points + are used in learning new chemicals from your possible list of + learn-able chemicals, along with learning chemicals from the + hosts blood for both their benefit and your objectives + +
+
+
+
+
+ ); +}; + +const BorerAbilities = (props: any, context: any) => { + const { act, data } = useBackend(context); + return ( + + + + + + ); +}; + +const AbilitySection = (props: any, context: any) => { + const { act, data } = useBackend(context); + const { ability } = data; + if (!ability) { + return
; + } + + const [selectedAbility, setSelectedAbility] = useLocalState( + context, + 'ability', + ability[0] + ); + + return ( +
+ }> + + + abilities.ability_name)} + onSelected={(abilityName: string) => + setSelectedAbility( + ability.find((p) => p.ability_name === abilityName) || + ability[0] + ) + } + /> + {selectedAbility && ( + + )} + + + + + {selectedAbility && selectedAbility.ability_explanation} + + +
+ ); +}; + +const DisadvantageInfo = () => { + return ( + + +
+ + + + Whilst in a host you can provide many benefits, but also + dangerous side-effects due to your sensitive brain manipulation. + Here's how to prevent them + +
+
+ + 1. Whilst inside of a host we will passivelly make their health + unable to be read due to our body obstructing the somatosensory + cortex signals + +
+ + Prevention method - observe the hosts health carefully using + "Check Blood", heal any injuries and inform the host + about any major wounds + +
+
+ + 2. Whilst inside of a host we will slowly deal toxin damage + over-time up to 80 in total. This can be deadly when combined + with any amount of brute/burn damage + +
+ + Prevention method - observe the hosts health carefully using + "Check Blood", inject toxin damage restoring chemicals + +
+
+ + 3. Whilst inside of a host most of our actions will deal brain + damage including generating evolution and chemical evolution + points, due to either sensetivelly manipulating the host's + neurons or needing to "aquire" more space for growth + +
+ + Prevention method - observe the hosts health carefully using + "Check Blood", inject mannitol to cure brain damage, + inject neurine for any brain traumas that might have been a + result of our expansion + +
+
+
+
+
+ ); +}; + +const BasicChemistry = () => { + return ( + + +
+ + + + Secreting chemicals has proven difficult for many borers, yet + you have prepared carefully for your first expendition into the + hosts body. Lets not mess it up by killing them. + +
+ + This is only the bare minimum of what we should get knowledgable + about + +
+
+ Libital +
+ + Quickly restores our hosts + Brute damage at the cost + of causing slight liver damage. + +
+
+ Lenturi +
+ + Quickly restores our hosts + Burn damage at the cost + of causing slight stomach damage and slowing down our host as + long as its in their system + +
+
+ Seiver +
+ + Heals Toxin damage at the + slight cost of heart damage + +
+
+ Convermol +
+ + Quickly restores our hosts + Oxygen damage at the cost + of causing 1:5th the toxin damage to our host + +
+ Overdose: 35 units +
+
+ Unknown Methamphetamine Isomer +
+ + A specially advanced version of what our hosts call + "meth". It has all the benefits of meth without + causing any brain damage to the host and has a higher overdose + +
+ Overdose: 40 units +
+
+ Spaceacillin +
+ + Helps our hosts immune system, making it quickly gain resistance + to any pathogens inside of the host. + +
+ + While being effective it will most likelly not be enough to + fully cure our host + +
+
+ multiver +
+ + Purges toxins and medicines inside of our host while healing + Toxin damage, at the + cost of slight lung damage. + +
+ + The more unique medicines the host has in their system, the more + this chemical heals. + +
+ At 2 unique medicines it no longer purges medicines +
+
+
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/BorerChem.js b/tgui/packages/tgui/interfaces/BorerChem.js new file mode 100644 index 000000000000..70a6bbee1ee3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BorerChem.js @@ -0,0 +1,60 @@ +// THIS IS A MONKESTATION UI FILE + +import { toFixed } from 'common/math'; +import { useBackend } from '../backend'; +import { Box, Button, LabeledList, ProgressBar, Section } from '../components'; +import { Window } from '../layouts'; + +export const BorerChem = (props, context) => { + const { act, data } = useBackend(context); + const borerTransferAmounts = data.borerTransferAmounts || []; + return ( + + +
+ + + + {toFixed(data.energy) + ' units'} + + + +
+
( +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/BorerEvolution.tsx b/tgui/packages/tgui/interfaces/BorerEvolution.tsx new file mode 100644 index 000000000000..82a941fd7a3a --- /dev/null +++ b/tgui/packages/tgui/interfaces/BorerEvolution.tsx @@ -0,0 +1,142 @@ +// THIS IS A MONKESTATION UI FILE + +import { useBackend } from '../backend'; +import { Section, Stack, Button, BlockQuote } from '../components'; +import { Window } from '../layouts'; + +const borerColor = { + fontWeight: 'bold', + color: '#b2c96b', +}; + +type Evolution = { + path: string; + name: string; + desc: string; + gainFlavor: string; + cost: number; + disabled: boolean; + evoPath: string; + color: string; + tier: number; + exclusive: boolean; +}; + +type EvolutionInfo = { + learnableEvolution: Evolution[]; + learnedEvolution: Evolution[]; +}; + +type Info = { + evolution_points: number; +}; + +export const BorerEvolution = (props, context) => { + return ( + + + + + + + + + + ); +}; + +const PastEvolutions = (props, context) => { + const { data } = useBackend(context); + const { learnedEvolution } = data; + + return ( + +
+ + {(!learnedEvolution.length && 'None!') || + learnedEvolution.map((learned) => ( + +
+
+ ); +}; + +const EvolutionList = (props, context) => { + const { data, act } = useBackend(context); + const { learnableEvolution } = data; + + return ( + +
+ {(!learnableEvolution.length && 'None!') || + learnableEvolution.map((toLearn) => ( + +
+
+ ); +}; + +const EvoInfo = (props, context) => { + const { data } = useBackend(context); + const { evolution_points } = data; + + return ( + + + + + You have {evolution_points || 0}  + + evolution point{evolution_points !== 1 ? 's' : ''} + {' '} + to spend. + + + + + + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/corticalborer.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/corticalborer.ts new file mode 100644 index 000000000000..4b87325a3990 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/corticalborer.ts @@ -0,0 +1,17 @@ +import { Antagonist, Category } from '../base'; +import { multiline } from 'common/string'; + +const CorticalBorer: Antagonist = { + key: 'corticalborer', + name: 'Cortical Borer', + description: [ + multiline` + You are a slug that crawls into peoples ears and + then manipulates them in various ways + to make sure your species survives and thrives + `, + ], + category: Category.Midround, +}; + +export default CorticalBorer; From a1379e335b1ec35a608ed2f9c93ce30ffa2ab4e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 01:30:11 +0000 Subject: [PATCH 4/4] Automatic changelog for PR #976 [ci skip] --- html/changelogs/AutoChangeLog-pr-976.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-976.yml diff --git a/html/changelogs/AutoChangeLog-pr-976.yml b/html/changelogs/AutoChangeLog-pr-976.yml new file mode 100644 index 000000000000..200401b777e3 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-976.yml @@ -0,0 +1,4 @@ +author: "Jake Park, Zonespace, Gboster" +delete-after: True +changes: + - rscadd: "Added a new antagonist: The cortical borer, capable of manipulating humans with the sole goal of making more of itself" \ No newline at end of file